Files
FTC25009/tools/mcp/sqlserver-mcp-server/src/db.ts
2026-06-24 09:13:18 +08:00

105 lines
2.8 KiB
TypeScript

import sql, { type config as SqlConfig, type ConnectionPool, type IResult, type Request } from "mssql";
import type { ServerConfig } from "./config.js";
import { quoteIdentifier, resolveDatabaseName, type SqlParam } from "./sql.js";
export type QueryResult = IResult<Record<string, unknown>>;
export class SqlServerClient {
private readonly pools = new Map<string, Promise<ConnectionPool>>();
constructor(private readonly config: ServerConfig) {}
get allowedDatabases(): string[] {
return this.config.allowedDatabases;
}
get defaultDatabase(): string {
return this.config.defaultDatabase;
}
get readOnly(): boolean {
return this.config.readOnly;
}
get enableDdl(): boolean {
return this.config.enableDdl;
}
get maxRows(): number {
return this.config.maxRows;
}
get maxWriteRows(): number {
return this.config.maxWriteRows;
}
async query(
databaseName: string | undefined,
statement: string,
params: SqlParam[] = []
): Promise<QueryResult> {
const pool = await this.getPool(databaseName);
const request = pool.request();
bindParams(request, params);
return request.query(statement) as Promise<QueryResult>;
}
async batch(databaseName: string | undefined, statement: string): Promise<QueryResult> {
const pool = await this.getPool(databaseName);
return pool.request().batch(statement) as Promise<QueryResult>;
}
async close(): Promise<void> {
const pools = await Promise.all([...this.pools.values()]);
await Promise.all(pools.map((pool) => pool.close()));
this.pools.clear();
}
private async getPool(requestedDatabase: string | undefined): Promise<ConnectionPool> {
const database = resolveDatabaseName(
requestedDatabase,
this.config.defaultDatabase,
this.config.allowedDatabases
);
const key = database.toLowerCase();
const existing = this.pools.get(key);
if (existing) {
return existing;
}
const poolPromise = new sql.ConnectionPool(buildSqlConfig(this.config, database)).connect();
this.pools.set(key, poolPromise);
return poolPromise;
}
}
export function useDatabaseStatement(databaseName: string): string {
return `USE ${quoteIdentifier(databaseName)};`;
}
function buildSqlConfig(config: ServerConfig, database: string): SqlConfig {
return {
server: config.serverName,
database,
user: config.user,
password: config.password,
connectionTimeout: config.connectionTimeoutMs,
requestTimeout: config.requestTimeoutMs,
options: {
encrypt: config.encrypt,
trustServerCertificate: config.trustServerCertificate
},
pool: {
max: 5,
min: 0,
idleTimeoutMillis: 30000
}
};
}
function bindParams(request: Request, params: SqlParam[]): void {
for (const param of params) {
request.input(param.name, param.value);
}
}