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
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,33 @@ Add to your Claude Desktop configuration (`claude_desktop_config.json`):

### Claude Code CLI

#### Windows

Add the following to `%USERPROFILE%\.claude.json`:

```json
{
"mcpServers": {
"sqlserver": {
"command": "mcp-sqlserver",
"env": {
"SQLSERVER_HOST": "your-server",
"SQLSERVER_USER": "your-username",
"SQLSERVER_PASSWORD": "your-password",
"SQLSERVER_DATABASE": "your-database",
"SQLSERVER_PORT": "1433",
"SQLSERVER_ENCRYPT": "true",
"SQLSERVER_TRUST_CERT": "false"
}
}
}
}
```

#### macOS/Linux

Add the server using the Claude Code CLI:

```bash
# Set environment variables
export SQLSERVER_HOST="your-server"
Expand All @@ -250,6 +277,27 @@ export SQLSERVER_PASSWORD="your-password"
claude mcp add sqlserver mcp-sqlserver
```

Or configure via `~/.claude.json`:

```json
{
"mcpServers": {
"sqlserver": {
"command": "mcp-sqlserver",
"env": {
"SQLSERVER_HOST": "your-server",
"SQLSERVER_USER": "your-username",
"SQLSERVER_PASSWORD": "your-password",
"SQLSERVER_DATABASE": "your-database",
"SQLSERVER_PORT": "1433",
"SQLSERVER_ENCRYPT": "true",
"SQLSERVER_TRUST_CERT": "false"
}
}
}
}
```

### VSCode with MCP Extension

Install the MCP extension for VSCode and add the server configuration.
Expand Down Expand Up @@ -325,12 +373,35 @@ npm test
## Troubleshooting

### Connection Issues

Use the `--test-connection` flag to diagnose connectivity problems:

```bash
# Test your connection (shows detailed diagnostics)
mcp-sqlserver --test-connection
# or use the short flag
mcp-sqlserver -t
```

The command provides helpful error suggestions based on the error type:

| Error Type | Likely Cause | Suggested Fix |
|------------|-------------|---------------|
| `ENOTFOUND` / Server not found | Incorrect hostname | Check `SQLSERVER_HOST` |
| Login failed (18456) | Wrong username/password | Verify credentials |
| Timeout | Server unreachable or wrong port | Check port (default: 1433) |
| SSL/Certificate error | Self-signed certificate | Set `SQLSERVER_TRUST_CERT=true` |
| Connection refused | Firewall or SQL Server not running | Check server is running |

### Before Troubleshooting

1. Verify server hostname and port
2. Check if encryption/certificate settings match your SQL Server configuration
3. Ensure user has appropriate read permissions
4. Test connection using SQL Server Management Studio first

### Permission Issues

The user account needs at minimum:
- `CONNECT` permission to the database
- `SELECT` permission on tables/views you want to query
Expand Down
12 changes: 12 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export default {
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.js$': '$1',
},
transform: {
'^.+\\.ts$': ['ts-jest', { useESM: true }],
},
testMatch: ['**/test/**/*.test.ts'],
collectCoverageFrom: ['src/**/*.ts', '!src/**/*.d.ts', '!src/__tests__/**'],
};
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -54,16 +54,18 @@
"zod": "^3.23.8"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/mssql": "^9.1.5",
"@types/node": "^22.0.0",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^9.0.0",
"jest": "^29.7.0",
"ts-jest": "^29.4.9",
"tsx": "^4.16.0",
"typescript": "^5.5.0"
},
"engines": {
"node": ">=18"
}
}
}
153 changes: 147 additions & 6 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#!/usr/bin/env node

import { ConnectionConfigSchema } from './types.js';
import { SqlServerConnection } from './connection.js';

function showHelp() {
console.log(`
Expand All @@ -11,12 +12,17 @@ USAGE:

ENVIRONMENT VARIABLES:
SQLSERVER_HOST SQL Server hostname (required)
SQLSERVER_USER Database username (required)
SQLSERVER_USER Database username (required)
SQLSERVER_PASSWORD Database password (required)
SQLSERVER_DATABASE Database name (optional, default: master)
SQLSERVER_PORT Port number (optional, default: 1433)
SQLSERVER_ENCRYPT Enable encryption (optional, default: true)
SQLSERVER_TRUST_CERT Trust server certificate (optional, default: true)
SQLSERVER_TRUST_CERT Trust server certificate (optional, default: false)

OPTIONS:
--help, -h Show this help message
--version, -v Show version number
--test-connection Test SQL Server connection and exit

EXAMPLES:
# Set environment variables and run
Expand All @@ -25,6 +31,9 @@ EXAMPLES:
export SQLSERVER_PASSWORD="your-password"
mcp-sqlserver

# Test connection
mcp-sqlserver --test-connection

# Using Claude Desktop (add to claude_desktop_config.json):
{
"mcpServers": {
Expand Down Expand Up @@ -62,13 +71,140 @@ For more information, visit: https://github.com/bilims/mcp-sqlserver

function showVersion() {
// Read version from package.json
console.log('2.0.1');
console.log('2.0.3');
}

async function testConnection(): Promise<void> {
const config = {
server: process.env.SQLSERVER_HOST || 'localhost',
user: process.env.SQLSERVER_USER || '',
password: process.env.SQLSERVER_PASSWORD || '',
database: process.env.SQLSERVER_DATABASE,
port: parseInt(process.env.SQLSERVER_PORT || '1433'),
encrypt: process.env.SQLSERVER_ENCRYPT !== 'false',
trustServerCertificate: process.env.SQLSERVER_TRUST_CERT !== 'false',
connectionTimeout: parseInt(process.env.SQLSERVER_CONNECTION_TIMEOUT || '15000'),
requestTimeout: parseInt(process.env.SQLSERVER_REQUEST_TIMEOUT || '30000'),
maxRows: parseInt(process.env.SQLSERVER_MAX_ROWS || '1000'),
};

// Validate config
try {
ConnectionConfigSchema.parse(config);
} catch (error) {
console.error('❌ Configuration error:');
console.error(error);
process.exit(1);
}

if (!config.user || !config.password) {
console.error('❌ Missing credentials:');
console.error(' SQLSERVER_USER and SQLSERVER_PASSWORD are required');
process.exit(1);
}

console.log(`\n🔍 Testing connection to ${config.server}:${config.port}/${config.database || 'default'}`);
console.log(` Encryption: ${config.encrypt ? 'enabled' : 'disabled'}`);
console.log(` Trust Certificate: ${config.trustServerCertificate ? 'yes' : 'no'}`);
console.log('');

const startTime = Date.now();
const connection = new SqlServerConnection(config);

try {
await connection.connect();
const connectionTime = Date.now() - startTime;

console.log('✅ Connection successful!');
console.log(` Connection time: ${connectionTime}ms`);
console.log(` Connected: ${connection.isConnected()}`);

// Try to get server info
try {
const result = await connection.query(`
SELECT
@@SERVERNAME as serverName,
@@VERSION as version,
DB_NAME() as currentDatabase
`);

if (result.recordset.length > 0) {
const info = result.recordset[0];
console.log(`\n📋 Server Info:`);
console.log(` Server: ${info.serverName}`);
console.log(` Database: ${info.currentDatabase}`);
// Version is multi-line, just show first line
const versionLine = info.version.split('\n')[0];
console.log(` Version: ${versionLine}`);
}
} catch (queryError) {
// Server info is nice-to-have, don't fail on this
}

await connection.disconnect();
process.exit(0);

} catch (error) {
const connectionTime = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : String(error);

console.error('❌ Connection failed!');
console.error(` Error: ${errorMessage}`);
console.error(` Time: ${connectionTime}ms`);

// Provide helpful suggestions based on error
const suggestion = getConnectionSuggestion(errorMessage);
if (suggestion) {
console.error(`\n💡 ${suggestion}`);
}

// Show environment info for debugging
console.error(`\n🔧 Environment:`);
console.error(` Host: ${config.server}:${config.port}`);
console.error(` Database: ${config.database || '(default)'}`);
console.error(` Encrypt: ${config.encrypt}`);
console.error(` Trust Cert: ${config.trustServerCertificate}`);

await connection.disconnect().catch(() => {});
process.exit(1);
}
}

function getConnectionSuggestion(errorMessage: string): string | null {
const msg = errorMessage.toLowerCase();

if (msg.includes('login failed') || msg.includes('18456')) {
return 'Check your username and password. SQL Server authentication failed.';
}
if (msg.includes('enotfound') || msg.includes('server was not found') || msg.includes('could not be located') || msg.includes('name or service not known')) {
return 'Check your server hostname. The SQL Server instance could not be found.';
}
if (msg.includes('timeout') || msg.includes('-2') || msg.includes('timed out')) {
return 'Connection timed out. Check if the server is reachable and the port is correct.';
}
if (msg.includes('ssl') || msg.includes('certificate') || msg.includes('tls') || msg.includes('ssl_ctx')) {
return 'SSL/Certificate error. Try setting SQLSERVER_TRUST_CERT=true if using a self-signed cert.';
}
if (msg.includes('encrypt') || msg.includes('handshake') || msg.includes('ssl_negotiate')) {
return 'Encryption handshake failed. Try SQLSERVER_ENCRYPT=false or check certificate configuration.';
}
if (msg.includes('port') || msg.includes('connection refused')) {
return 'Connection refused. Check your port number and ensure SQL Server is running.';
}
if (msg.includes('named pipes')) {
return 'Named pipes error. Try using TCP/IP connection instead.';
}
if (msg.includes('permission') || msg.includes('access') || msg.includes('denied')) {
return 'Permission denied. Your user may not have access to this database.';
}

return null;
}

function validateEnvironment(): boolean {
const required = ['SQLSERVER_HOST', 'SQLSERVER_USER', 'SQLSERVER_PASSWORD'];
const missing = required.filter(env => !process.env[env]);

if (missing.length > 0) {
console.error('❌ Missing required environment variables:');
missing.forEach(env => {
Expand Down Expand Up @@ -100,17 +236,22 @@ function validateEnvironment(): boolean {

export function handleCliArgs(): boolean {
const args = process.argv.slice(2);

if (args.includes('--help') || args.includes('-h')) {
showHelp();
return false;
}

if (args.includes('--version') || args.includes('-v')) {
showVersion();
return false;
}

if (args.includes('--test-connection') || args.includes('-t')) {
testConnection();
return false; // testConnection exits on its own
}

if (!validateEnvironment()) {
process.exit(1);
}
Expand Down
5 changes: 4 additions & 1 deletion src/connection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,9 @@ export class SqlServerConnection {
}

getConfig(): Readonly<ConnectionConfig> {
return { ...this.config };
// Return config with password redacted for security
const redactedConfig = { ...this.config };
redactedConfig.password = '********';
return redactedConfig;
}
}
2 changes: 1 addition & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ async function runServer() {
// Don't connect immediately in MCP mode - defer connection until first tool use
// This prevents the server from failing startup if SQL Server is temporarily unavailable
console.error(`MCP SQL Server initialized for ${config.server}:${config.port || 1433}`);
console.error(`Database: ${config.database || 'default'}, User: ${config.user}`);
console.error(`Database: ${config.database || 'default'}`);

this.initializeTools(config.maxRows || 1000);
} catch (error) {
Expand Down
Loading