Skip to content

Commit 3812a38

Browse files
tianzhouclaude
andauthored
feat: add description field for TOML sources (#232)
Add optional description field to source configurations that serves as human documentation and is exposed via /api/sources for MCP clients. Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
1 parent bdc906c commit 3812a38

File tree

8 files changed

+66
-0
lines changed

8 files changed

+66
-0
lines changed

dbhub.toml.example

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Local PostgreSQL - DSN format (typical Docker/local dev setup)
1919
[[sources]]
2020
id = "local_pg"
21+
description = "Local development database"
2122
dsn = "postgres://postgres:postgres@localhost:5432/myapp"
2223

2324
# Local PostgreSQL - Individual parameters (use when password contains special characters like @, :, /)
@@ -249,6 +250,11 @@ dsn = "postgres://postgres:postgres@localhost:5432/myapp"
249250
# QUICK REFERENCE
250251
# ============================================================================
251252
#
253+
# Source Fields:
254+
# id = "unique_id" # Required: unique identifier
255+
# description = "..." # Optional: human-readable description
256+
# dsn = "..." # Connection string (or use individual params below)
257+
#
252258
# DSN Formats:
253259
# PostgreSQL: postgres://user:pass@host:5432/database?sslmode=require
254260
# MySQL: mysql://user:pass@host:3306/database

src/__fixtures__/toml/readonly-maxrows.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
[[sources]]
55
id = "readonly_limited"
6+
description = "Read-only database for safe queries"
67
type = "sqlite"
78
database = ":memory:"
89

src/api/__tests__/sources.integration.test.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,19 @@ describe('Data Sources API Integration Tests', () => {
211211
expect(sqlParam!.description).toContain('SQL');
212212
});
213213
});
214+
215+
it('should include description when present', async () => {
216+
const response = await fetch(`${BASE_URL}/api/sources`);
217+
const sources = (await response.json()) as DataSource[];
218+
219+
// First source has a description
220+
const readonlySource = sources.find(s => s.id === 'readonly_limited');
221+
expect(readonlySource?.description).toBe('Read-only database for safe queries');
222+
223+
// Other sources don't have descriptions
224+
const writableSource = sources.find(s => s.id === 'writable_limited');
225+
expect(writableSource?.description).toBeUndefined();
226+
});
214227
});
215228

216229
describe('GET /api/sources/{source-id}', () => {

src/api/openapi.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ export interface components {
5454
* @example prod_pg
5555
*/
5656
id: string;
57+
/**
58+
* @description Human-readable description of the data source
59+
* @example Production read replica for analytics queries
60+
*/
61+
description?: string;
5762
/**
5863
* @description Database type
5964
* @example postgres

src/api/openapi.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ components:
9696
type: string
9797
description: Unique identifier for the data source
9898
example: prod_pg
99+
description:
100+
type: string
101+
description: Human-readable description of the data source
102+
example: Production read replica for analytics queries
99103
type:
100104
type: string
101105
enum: [postgres, mysql, mariadb, sqlserver, sqlite]

src/api/sources.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ function transformSourceConfig(source: SourceConfig): DataSource {
3131
type: source.type,
3232
};
3333

34+
// Add description if present
35+
if (source.description) {
36+
dataSource.description = source.description;
37+
}
38+
3439
// Add connection details (excluding password)
3540
if (source.host) {
3641
dataSource.host = source.host;

src/config/__tests__/toml-loader.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,37 @@ dsn = "postgres://user:pass@localhost:5432/testdb"
397397
});
398398
});
399399

400+
describe('description field', () => {
401+
it('should parse description field', () => {
402+
const tomlContent = `
403+
[[sources]]
404+
id = "test_db"
405+
description = "Production read replica for analytics"
406+
dsn = "postgres://user:pass@localhost:5432/testdb"
407+
`;
408+
fs.writeFileSync(path.join(tempDir, 'dbhub.toml'), tomlContent);
409+
410+
const result = loadTomlConfig();
411+
412+
expect(result).toBeTruthy();
413+
expect(result?.sources[0].description).toBe('Production read replica for analytics');
414+
});
415+
416+
it('should work without description (optional field)', () => {
417+
const tomlContent = `
418+
[[sources]]
419+
id = "test_db"
420+
dsn = "postgres://user:pass@localhost:5432/testdb"
421+
`;
422+
fs.writeFileSync(path.join(tempDir, 'dbhub.toml'), tomlContent);
423+
424+
const result = loadTomlConfig();
425+
426+
expect(result).toBeTruthy();
427+
expect(result?.sources[0].description).toBeUndefined();
428+
});
429+
});
430+
400431
describe('sslmode validation', () => {
401432
it('should accept sslmode = "disable"', () => {
402433
const tomlContent = `

src/types/config.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ export interface ConnectionParams {
4141
*/
4242
export interface SourceConfig extends ConnectionParams, SSHConfig {
4343
id: string;
44+
description?: string; // Human-readable description of this data source
4445
dsn?: string;
4546
connection_timeout?: number; // Connection timeout in seconds
4647
query_timeout?: number; // Query timeout in seconds (PostgreSQL, MySQL, MariaDB, SQL Server)

0 commit comments

Comments
 (0)