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
93 changes: 93 additions & 0 deletions MssqlMcp/Node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,12 +92,14 @@ This server leverages the Model Context Protocol (MCP), a versatile framework th
"env": {
"SERVER_NAME": "your-server-name.database.windows.net",
"DATABASE_NAME": "your-database-name",
"AUTH_METHOD": "azure-ad",
"READONLY": "false"
}
}
}
}
}
}
```
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra closing brace that creates invalid JSON syntax. This should be removed to maintain proper JSON structure.

Suggested change
```
}

Copilot uses AI. Check for mistakes.


4. **Restart VS Code**
Expand Down Expand Up @@ -126,25 +128,116 @@ This server leverages the Model Context Protocol (MCP), a versatile framework th
"env": {
"SERVER_NAME": "your-server-name.database.windows.net",
"DATABASE_NAME": "your-database-name",
"AUTH_METHOD": "azure-ad",
"READONLY": "false"
}
}
}
}
}
```
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's an extra closing brace that creates invalid JSON syntax. This should be removed to maintain proper JSON structure.

Suggested change
```
}

Copilot uses AI. Check for mistakes.


3. **Restart Claude Desktop**
- Close and reopen Claude Desktop for the changes to take effect

## Authentication Configuration

The MCP server supports multiple authentication methods to connect to your MSSQL database. Choose the method that best fits your environment and security requirements.

### Authentication Methods

#### 1. Azure Active Directory (Default)
**Best for:** Azure SQL Database, managed identities, modern security requirements

This method uses Azure AD authentication with interactive browser login. No username/password needed in configuration.

```json
{
"env": {
"SERVER_NAME": "your-server.database.windows.net",
"DATABASE_NAME": "your-database-name",
"AUTH_METHOD": "azure-ad"
}
}
```

**Required Environment Variables:**
- `AUTH_METHOD`: Set to `"azure-ad"` or `"azuread"`
- `SERVER_NAME`: Your Azure SQL server name
- `DATABASE_NAME`: Your database name

**Notes:**
- Uses interactive browser authentication
- Automatically handles token refresh
- Requires Azure AD permissions for the database
- Most secure option for Azure SQL Database

#### 2. SQL Server Authentication
**Best for:** Traditional SQL Server instances, local development

Uses SQL Server's built-in authentication with username and password.

```json
{
"env": {
"SERVER_NAME": "your-server.database.windows.net",
"DATABASE_NAME": "your-database-name",
"AUTH_METHOD": "sql",
"SQL_USERNAME": "your-sql-username",
"SQL_PASSWORD": "your-sql-password"
}
}
```

**Required Environment Variables:**
- `AUTH_METHOD`: Set to `"sql"` or `"sqlserver"`
- `SERVER_NAME`: Your SQL Server instance
- `DATABASE_NAME`: Your database name
- `SQL_USERNAME`: SQL Server username
- `SQL_PASSWORD`: SQL Server password

#### 3. Windows Authentication (NTLM)
**Best for:** On-premises SQL Server with Windows domain authentication

Uses Windows credentials for authentication.

```json
{
"env": {
"SERVER_NAME": "your-server-instance",
"DATABASE_NAME": "your-database-name",
"AUTH_METHOD": "windows",
"DOMAIN": "your-domain",
"USERNAME": "your-windows-username",
"PASSWORD": "your-windows-password"
}
}
```

**Required Environment Variables:**
- `AUTH_METHOD`: Set to `"windows"` or `"ntlm"`
- `SERVER_NAME`: Your SQL Server instance
- `DATABASE_NAME`: Your database name
- `DOMAIN`: Windows domain (optional, can be empty)
- `USERNAME`: Windows username
- `PASSWORD`: Windows password

### Configuration Parameters

#### Common Parameters (All Authentication Methods)
- **SERVER_NAME**: Your MSSQL Database server name (e.g., `my-server.database.windows.net`)
- **DATABASE_NAME**: Your database name
- **AUTH_METHOD**: Authentication method to use (`"azure-ad"`, `"sql"`, or `"windows"`). Defaults to `"azure-ad"` if not specified.
- **READONLY**: Set to `"true"` to restrict to read-only operations, `"false"` for full access
- **Path**: Update the path in `args` to point to your actual project location.
- **CONNECTION_TIMEOUT**: (Optional) Connection timeout in seconds. Defaults to `30` if not set.
- **TRUST_SERVER_CERTIFICATE**: (Optional) Set to `"true"` to trust self-signed server certificates (useful for development or when connecting to servers with self-signed certs). Defaults to `"false"`.

#### Authentication-Specific Parameters
- **SQL_USERNAME** & **SQL_PASSWORD**: Required for SQL Server authentication (`AUTH_METHOD="sql"`)
- **USERNAME**, **PASSWORD** & **DOMAIN**: Required for Windows authentication (`AUTH_METHOD="windows"`)
- **No additional parameters needed**: For Azure AD authentication (`AUTH_METHOD="azure-ad"`)

## Sample Configurations

You can find sample configuration files in the `src/samples/` folder:
Expand Down
132 changes: 94 additions & 38 deletions MssqlMcp/Node/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,36 +29,77 @@ let globalSqlPool: sql.ConnectionPool | null = null;
let globalAccessToken: string | null = null;
let globalTokenExpiresOn: Date | null = null;

// Function to create SQL config with fresh access token, returns token and expiry
export async function createSqlConfig(): Promise<{ config: sql.config, token: string, expiresOn: Date }> {
const credential = new InteractiveBrowserCredential({
redirectUri: 'http://localhost'
// disableAutomaticAuthentication : true
});
const accessToken = await credential.getToken('https://database.windows.net/.default');

// Function to create SQL config with configurable authentication method
export async function createSqlConfig(): Promise<{ config: sql.config, token?: string, expiresOn?: Date }> {
const authMethod = process.env.AUTH_METHOD?.toLowerCase() || 'azure-ad';
const trustServerCertificate = process.env.TRUST_SERVER_CERTIFICATE?.toLowerCase() === 'true';
const connectionTimeout = process.env.CONNECTION_TIMEOUT ? parseInt(process.env.CONNECTION_TIMEOUT, 10) : 30;

return {
config: {
server: process.env.SERVER_NAME!,
database: process.env.DATABASE_NAME!,
options: {
encrypt: true,
trustServerCertificate
},
authentication: {
type: 'azure-active-directory-access-token',
options: {
token: accessToken?.token!,
},
},
connectionTimeout: connectionTimeout * 1000, // convert seconds to milliseconds
const baseConfig = {
server: process.env.SERVER_NAME!,
database: process.env.DATABASE_NAME!,
options: {
encrypt: true,
trustServerCertificate,
useUTC: false
},
token: accessToken?.token!,
expiresOn: accessToken?.expiresOnTimestamp ? new Date(accessToken.expiresOnTimestamp) : new Date(Date.now() + 30 * 60 * 1000)
connectionTimeout: connectionTimeout * 1000, // convert seconds to milliseconds
};

switch (authMethod) {
case 'azure-ad':
case 'azuread':
// Azure Active Directory authentication
const credential = new InteractiveBrowserCredential({
redirectUri: 'http://localhost'
});
const accessToken = await credential.getToken('https://database.windows.net/.default');

return {
config: {
...baseConfig,
authentication: {
type: 'azure-active-directory-access-token',
options: {
token: accessToken?.token!,
},
},
},
token: accessToken?.token!,
expiresOn: accessToken?.expiresOnTimestamp ? new Date(accessToken.expiresOnTimestamp) : new Date(Date.now() + 30 * 60 * 1000)
};

case 'windows':
case 'ntlm':
// Windows authentication
return {
config: {
...baseConfig,
authentication: {
type: 'ntlm',
options: {
domain: process.env.DOMAIN || '',
userName: process.env.USERNAME || '',
password: process.env.PASSWORD || ''
}
},
}
};

case 'sql':
case 'sqlserver':
// SQL Server authentication
return {
config: {
...baseConfig,
user: process.env.SQL_USERNAME!,
password: process.env.SQL_PASSWORD!,
}
};
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the non-null assertion operator (!) on environment variables without validation could cause runtime errors if the required variables are not set. Consider adding validation to ensure SQL_USERNAME and SQL_PASSWORD are defined before using them.

Suggested change
};
// SQL Server authentication
if (!process.env.SQL_USERNAME) {
throw new Error("Environment variable SQL_USERNAME is required for SQL Server authentication.");
}
if (!process.env.SQL_PASSWORD) {
throw new Error("Environment variable SQL_PASSWORD is required for SQL Server authentication.");
}
return {
config: {
...baseConfig,
user: process.env.SQL_USERNAME,
password: process.env.SQL_PASSWORD,
}
};

Copilot uses AI. Check for mistakes.

Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using the non-null assertion operator (!) on environment variables without validation could cause runtime errors if the required variables are not set. Consider adding validation to ensure SQL_USERNAME and SQL_PASSWORD are defined before using them.

Suggested change
};
// SQL Server authentication
if (!process.env.SQL_USERNAME) {
throw new Error("Environment variable SQL_USERNAME is required for SQL Server authentication but is not set.");
}
if (!process.env.SQL_PASSWORD) {
throw new Error("Environment variable SQL_PASSWORD is required for SQL Server authentication but is not set.");
}
return {
config: {
...baseConfig,
user: process.env.SQL_USERNAME,
password: process.env.SQL_PASSWORD,
}
};

Copilot uses AI. Check for mistakes.


default:
throw new Error(`Unsupported authentication method: ${authMethod}. Supported methods: azure-ad, windows, sql`);
Copy link
Preview

Copilot AI Aug 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The error message lists 'azure-ad' as a supported method but the code also accepts 'azuread' as an alias. Consider updating the error message to include all accepted values: 'azure-ad/azuread, windows/ntlm, sql/sqlserver'.

Suggested change
throw new Error(`Unsupported authentication method: ${authMethod}. Supported methods: azure-ad, windows, sql`);
throw new Error(`Unsupported authentication method: ${authMethod}. Supported methods: azure-ad/azuread, windows/ntlm, sql/sqlserver`);

Copilot uses AI. Check for mistakes.

}
}

const updateDataTool = new UpdateDataTool();
Expand Down Expand Up @@ -164,21 +205,36 @@ runServer().catch((error) => {
// Connect to SQL only when handling a request

async function ensureSqlConnection() {
// If we have a pool and it's connected, and the token is still valid, reuse it
if (
globalSqlPool &&
globalSqlPool.connected &&
globalAccessToken &&
globalTokenExpiresOn &&
globalTokenExpiresOn > new Date(Date.now() + 2 * 60 * 1000) // 2 min buffer
) {
return;
const authMethod = process.env.AUTH_METHOD?.toLowerCase() || 'azure-ad';

// For Azure AD, check token expiry; for other methods, just check connection
if (authMethod === 'azure-ad' || authMethod === 'azuread') {
// If we have a pool and it's connected, and the token is still valid, reuse it
if (
globalSqlPool &&
globalSqlPool.connected &&
globalAccessToken &&
globalTokenExpiresOn &&
globalTokenExpiresOn > new Date(Date.now() + 2 * 60 * 1000) // 2 min buffer
) {
return;
}
} else {
// For Windows and SQL Server auth, just check if connection exists
if (globalSqlPool && globalSqlPool.connected) {
return;
}
}

// Otherwise, get a new token and reconnect
const { config, token, expiresOn } = await createSqlConfig();
globalAccessToken = token;
globalTokenExpiresOn = expiresOn;
// Get new config (and token if using Azure AD)
const result = await createSqlConfig();
const { config } = result;

// Store token info for Azure AD
if (authMethod === 'azure-ad' || authMethod === 'azuread') {
globalAccessToken = result.token!;
globalTokenExpiresOn = result.expiresOn!;
}

// Close old pool if exists
if (globalSqlPool && globalSqlPool.connected) {
Expand Down