damoxing-datasource-mcp
README.md
# @damoxing/datasource-manager
中文文档: `README.zh-CN.md`
HTTP-agnostic multi-datasource lifecycle manager.
It owns:
- datasource config loading
- datasource status tracking
- generic route-key resolution, with `jgbh` compatibility
- heartbeat
- pool recovery
- one-shot retry for connection errors
- graceful shutdown
The core manager is not bound to HTTP. It can run inside Express, a worker, a CLI, or another Node.js service.
Database adapters are included for Oracle, OceanBase (Oracle / MySQL Mode), MySQL, DM, PostgreSQL, GaussDB/openGauss, and Kingbase. Drivers are optional peer dependencies and are loaded only when the corresponding adapter is used.
## Adapter Contract
An adapter instance should expose:
```js
{
isOracle: Boolean,
adapterType: String,
initialize: async () => {},
close: async () => {},
all: async (sql, params) => [],
get: async (sql, params) => row,
run: async (sql, params) => result,
exec: async (sql) => {},
transaction: async (work) => result
}
```
`withConnection(callback)` is optional.
## Usage
CommonJS:
```js
const {
DataSourceManager,
createDefaultAdapterFactories
} = require('@damoxing/datasource-manager');
const manager = new DataSourceManager({
config,
logger,
shutdownSignals: ['SIGTERM'],
adapterFactories: createDefaultAdapterFactories(logger),
});
await manager.initialize();
const db = manager.getByJgbh('1001');
const row = await db.get('SELECT 1 AS health_check');
await manager.closeAll();
```
ESM:
```js
import {
DataSourceManager,
createDefaultAdapterFactories
} from '@damoxing/datasource-manager';
const manager = new DataSourceManager({
config,
logger,
adapterFactories: createDefaultAdapterFactories(logger),
});
```
Use `getByRouteKey()` for new non-business-specific code:
```js
const db = manager.getByRouteKey('tenant-a');
```
`getByJgbh()` remains as a compatibility wrapper.
## MCP Server For Codex
This package includes a stdio MCP server so Codex can inspect configured datasources through the same manager and adapter layer:
```bash
npm run build
DAMOXING_MCP_CONFIG=/absolute/path/to/datasources.json node dist/cjs/mcp/server.js
```
After package installation, the bin entry is:
```bash
damoxing-datasource-mcp
```
The MCP server exposes health, read-only query, table/routine metadata, explain-plan tools, and MCP-only fixed physical sessions:
- `damoxing_open_session`
- `damoxing_session_query`
- `damoxing_session_exec`
- `damoxing_session_commit`
- `damoxing_session_rollback`
- `damoxing_cancel_session_operation`
- `damoxing_get_notices`
- `damoxing_get_audit_events`
- `damoxing_close_session`
All operations carrying the same `sessionId` use the same leased database connection and are serialized. PostgreSQL-compatible sessions run inside an explicit transaction; close and abnormal connection cleanup roll back before releasing the lease. A lost fixed connection is never retried on another connection.
Fixed sessions are read-only by default. Opening a read/write session requires `DAMOXING_MCP_ALLOW_WRITE=true` and `confirmWrite=true`. Datasource ids listed in `DAMOXING_MCP_PRODUCTION_DATASOURCES` are denied unless `DAMOXING_MCP_ALLOW_PRODUCTION_DEBUG=true` is set and the call passes `confirmProduction=true`. Destructive SQL additionally requires `DAMOXING_MCP_ALLOW_DESTRUCTIVE=true` and `confirmDestructive=true`.
Session policy can be bounded with:
- `DAMOXING_MCP_MAX_SESSIONS` (default `5`)
- `DAMOXING_MCP_MAX_SESSIONS_PER_DATASOURCE` (default `2`)
- `DAMOXING_MCP_SESSION_IDLE_TIMEOUT_MS` (default `300000`)
- `DAMOXING_MCP_SESSION_MAX_LIFETIME_MS` (default `1800000`)
- `DAMOXING_MCP_SESSION_CLEANUP_INTERVAL_MS` (default `30000`)
- `DAMOXING_MCP_AUDIT_MAX_EVENTS` (default `1000`)
The MCP session registry keeps a bounded in-memory audit trail. Audit events contain action, outcome, datasource/session identifiers, elapsed time, row counts, SQL kind, a normalized SQL fingerprint, and parameter counts/names. SQL text and parameter values are never stored. Use `damoxing_get_audit_events` to retrieve the redacted events.
The fixed-session manager, registry, timer, and reserved connections are created lazily by the MCP entry point. Importing `@damoxing/datasource-manager` directly does not load the MCP SDK or create MCP resources.
Fixed-session integration results from the local OrbStack lab on July 15, 2026:
| Database | Fixed connection / transaction | Session state | Notices | Timeout / cancellation |
| --- | --- | --- | --- | --- |
| PostgreSQL | Passed | Temporary tables and session variables passed | Passed | Native `statement_timeout` and explicit cancellation passed |
| openGauss | Passed | Temporary tables and session variables passed | Passed | Native `statement_timeout` and explicit cancellation passed |
| Oracle | Passed | Stable SID and `CLIENT_INFO` passed | PostgreSQL NOTICE semantics unavailable | `callTimeout` passed and the connection remained usable |
| DM | Passed | Stable session id and global temporary table passed | Not exposed by the current driver | The current `dmdb` driver has no reliable cancellation/operation-timeout API; MCP reports unsupported |
| Kingbase | Pending | Local container database process cannot start because its development license has expired | Not verified | Not verified |
Run `npm run test:integration:session:pg`, `npm run test:integration:session:opengauss`, `npm run test:integration:session:oracle`, and `npm run test:integration:session:dm` to repeat the verified matrix.
This phase is the fixed-session foundation, not complete stored-routine debugging. Structured OUT/INOUT values, cursor handles/fetch, routine profiling, and native breakpoints remain roadmap work.
The repository skill lives at `skills/damoxing-database-mcp`.
## Health Output
`getHealth()` returns a stable, sanitized schema:
```js
{
generatedAt,
initialized,
strictRouting,
defaultDatasourceId,
routeCount,
heartbeat: {
enabled,
running,
intervalMs
},
shutdownHooks: {
installed,
signals
},
summary: {
total,
ready,
failed,
unhealthy,
byStatus: {
configured,
initializing,
ready,
unhealthy,
failed,
recovering,
closed
}
},
datasources: [
{
id,
type,
status,
jgbhCount,
routeKeyCount,
isDefault,
lastHeartbeatAt,
lastReadyAt,
lastRecoverAt,
lastStatusChangeAt,
lastError
}
]
}
```
Datasource config is never included in health output, so passwords and connect strings are not exposed.
## Logging
Datasource errors are logged with structured context as the second logger argument:
```js
{
datasourceId,
type,
jgbh,
jgbhList,
routeKeys,
status,
lastError,
error
}
```
SQL text logging is enabled by default. SQL parameter logging is disabled by default to avoid leaking production secrets.
Use adapter options to control parameter logging:
```js
const adapterFactories = createDefaultAdapterFactories({
logger,
logSql: true,
logParams: 'redacted',
redactKeys: ['password', 'token', 'secret'],
redactValue: '[REDACTED]'
});
```
`logParams` accepts:
- `false` or `'off'`: do not log SQL parameters
- `true` or `'redacted'`: log parameters with sensitive keys redacted
- `'raw'`: log raw parameters for local debugging only
## Graceful Shutdown
Use `shutdownSignals` to close all pools when the process receives a signal:
```js
const manager = new DataSourceManager({
shutdownSignals: ['SIGTERM', 'SIGINT'],
exitOnShutdownSignal: true,
adapterFactories,
config
});
```
`closeAll()` stops heartbeat timers, removes shutdown hooks, closes adapters, and marks datasources as `closed`.
## Config Shape
```json
{
"strict_routing": true,
"default_datasource": "oracle_main",
"datasources": [
{
"id": "oracle_main",
"type": "oracle",
"jgbh_list": ["1001"],
"route_keys": ["tenant-a"],
"config": {}
}
]
}
```
## Pool Governance
Use normalized pool options at datasource level or under `config.pool`:
```json
{
"id": "pg_main",
"type": "pg",
"pool": {
"minPoolSize": 1,
"maxPoolSize": 10,
"acquireTimeoutMs": 3000,
"connectTimeoutMs": 3000,
"idleTimeoutMs": 60000,
"maxLifetimeMs": 1800000,
"keepaliveMs": 30000
},
"config": {}
}
```
The manager maps supported options to each driver and logs unsupported options with datasource context. Invalid values, such as `minPoolSize > maxPoolSize`, fail during config build.
`applyPoolGovernance(type, config, pool)` is exported for tests and diagnostics.
## Metrics
`getMetrics()` returns counters, gauges, and latency summaries without SQL text, parameters, passwords, or connection strings:
```js
const metrics = manager.getMetrics();
```
Metrics currently track initialization, heartbeat, recovery, query success/failure, connection errors, retries, and transaction connection errors.
## Runtime Governance
Datasources can be changed at runtime:
```js
await manager.addDatasource({ id: 'tenant_a', type: 'pg', route_keys: ['TENANT_A'], config: {} });
await manager.updateDatasource('tenant_a', { id: 'tenant_a', type: 'pg', route_keys: ['TENANT_A'], config: {} });
await manager.removeDatasource('tenant_a');
await manager.reloadConfig(nextConfig);
```
`updateDatasource()` initializes and pings the replacement before closing the old pool. If replacement initialization fails, the old datasource remains active.
## Read/Write Groups
Group routing is HTTP-agnostic:
```json
{
"groups": {
"tenant_a": {
"write": "tenant_a_master",
"read": [
"tenant_a_read_1",
{ "id": "tenant_a_read_2", "weight": 2 }
],
"strategy": "round-robin",
"fallbackToWrite": true
}
}
}
```
```js
const readDb = manager.getReadHandle('tenant_a');
const writeDb = manager.getWriteHandle('tenant_a');
```
Read strategies are `round-robin`, `random`, `weighted`, and `first-ready`. Unhealthy read replicas are skipped.
## Config Secrets
Config values support environment interpolation, environment references, secret resolver hooks, and encrypted values:
```js
const manager = new DataSourceManager({
env: process.env,
secretResolver: async key => loadSecret(key),
decryptor: async value => decrypt(value),
config
});
```
```json
{
"password": "env:DB_PASSWORD",
"token": "secret:database/token",
"connectString": "postgres://app:${DB_PASSWORD}@localhost/db",
"encrypted": "ENC(ciphertext)"
}
```
Resolved secrets are redacted from health output, metrics output, structured datasource error state, and manager logs.
## Retry Rule
Connection-like errors may be retried once after pool recovery.
Transactions are not replayed automatically. If a transaction fails with a connection error, the manager rebuilds the pool and rethrows the original error.
## npm Packaging
This package is authored in TypeScript under `src/` and builds both module formats:
- CommonJS: `dist/cjs/index.js`
- ESM: `dist/esm/index.mjs`
- Types: `dist/types/index.d.ts`
Build locally before publishing:
```bash
npm install
npm run release:check
```
The ESM output is compiled from TypeScript with esbuild. Root and adapter aggregate imports keep database drivers lazy, so importing the package does not immediately load `oracledb`, `dmdb`, or `pg`.
Built-in database drivers are declared as optional peer dependencies:
- `oracledb` for Oracle
- `mysql2` for MySQL and both OceanBase Oracle/MySQL tenant modes
- `dmdb` for DM
- `pg` for PostgreSQL, GaussDB/openGauss, and Kingbase
Importing the package root does not immediately load these drivers. Accessing adapter classes or creating an adapter through `createDefaultAdapterFactories()` loads only the driver needed by that datasource type.
See `RELEASE.md` for semver, registry, token, provenance, and final publish checklist guidance.
See `ROADMAP.md` for the enterprise datasource framework backlog and priority order.
This server cannot be deployed
Maintenance
ActivitySlowing
ResponsivenessNo issues