Skip to content

Commit 7c3d99c

Browse files
committed
feat: add —demo mode
1 parent 658ea7f commit 7c3d99c

File tree

8 files changed

+153
-20
lines changed

8 files changed

+153
-20
lines changed

README.md

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,18 @@ docker run --rm --init \
6262
--transport sse \
6363
--port 8080 \
6464
--dsn "postgres://user:password@localhost:5432/dbname?sslmode=disable"
65+
```
66+
67+
```bash
68+
# Demo mode with sample employee database
69+
docker run --rm --init \
70+
--name dbhub \
71+
--publish 8080:8080 \
72+
bytebase/dbhub \
73+
--transport sse \
74+
--port 8080 \
75+
--demo
76+
```
6577

6678
### NPM
6779

@@ -70,14 +82,19 @@ docker run --rm --init \
7082
npx @bytebase/dbhub --transport sse --port 8080 --dsn "postgres://user:password@localhost:5432/dbname"
7183
```
7284

85+
```bash
86+
# Demo mode with sample employee database
87+
npx @bytebase/dbhub --transport sse --port 8080 --demo
88+
```
89+
7390
### Claude Desktop
7491

7592
![claude-desktop](https://raw.githubusercontent.com/bytebase/dbhub/main/resources/images/claude-desktop.webp)
7693

7794
- Claude Desktop only supports `stdio` transport https://github.com/orgs/modelcontextprotocol/discussions/16
7895

7996
```json
80-
// claude_desktop_config.json - PostgreSQL example
97+
// claude_desktop_config.json
8198
{
8299
"mcpServers": {
83100
"dbhub-postgres-docker": {
@@ -104,6 +121,16 @@ npx @bytebase/dbhub --transport sse --port 8080 --dsn "postgres://user:password@
104121
"--dsn",
105122
"postgres://user:password@localhost:5432/dbname?sslmode=disable"
106123
]
124+
},
125+
"dbhub-demo": {
126+
"command": "npx",
127+
"args": [
128+
"-y",
129+
"@bytebase/dbhub",
130+
"--transport",
131+
"stdio",
132+
"--demo"
133+
]
107134
}
108135
}
109136
}
@@ -120,7 +147,13 @@ npx @bytebase/dbhub --transport sse --port 8080 --dsn "postgres://user:password@
120147

121148
### Configure your database connection
122149

123-
Database Source Name (DSN) is required to connect to your database. You can provide this in several ways:
150+
You can use DBHub in demo mode with a sample employee database for testing:
151+
152+
```bash
153+
pnpm dev --demo
154+
```
155+
156+
For real databases, a Database Source Name (DSN) is required. You can provide this in several ways:
124157

125158
- **Command line argument** (highest priority):
126159

@@ -168,7 +201,8 @@ DBHub supports the following database connection string formats:
168201

169202
| Option | Description | Default |
170203
| :-------- | :-------------------------------------------------------------- | :---------------------------------- |
171-
| dsn | Database connection string | Required if not set via environment |
204+
| demo | Run in demo mode with sample employee database | `false` |
205+
| dsn | Database connection string | Required if not in demo mode |
172206
| transport | Transport mode: `stdio` or `sse` | `stdio` |
173207
| port | HTTP server port (only applicable when using `--transport=sse`) | `8080` |
174208

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "dbhub",
3-
"version": "0.0.7",
3+
"version": "0.0.8",
44
"description": "Universal Database MCP Server",
55
"main": "dist/index.js",
66
"type": "module",

src/config/demo-loader.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* Demo data loader for SQLite in-memory database
3+
*
4+
* This module loads the sample employee database into the SQLite in-memory database
5+
* when the --demo flag is specified.
6+
*/
7+
import fs from 'fs';
8+
import path from 'path';
9+
import { fileURLToPath } from 'url';
10+
11+
// Create __dirname equivalent for ES modules
12+
const __filename = fileURLToPath(import.meta.url);
13+
const __dirname = path.dirname(__filename);
14+
15+
// Path to sample data files
16+
const DEMO_DATA_DIR = path.join(__dirname, '..', '..', 'resources', 'employee-sqlite');
17+
18+
/**
19+
* Load SQL file contents
20+
*/
21+
export function loadSqlFile(fileName: string): string {
22+
const filePath = path.join(DEMO_DATA_DIR, fileName);
23+
return fs.readFileSync(filePath, 'utf8');
24+
}
25+
26+
/**
27+
* Get SQLite DSN for in-memory database
28+
*/
29+
export function getInMemorySqliteDSN(): string {
30+
return 'sqlite::memory:';
31+
}
32+
33+
/**
34+
* Load SQL files sequentially
35+
*/
36+
export function getSqliteInMemorySetupSql(): string {
37+
// First, load the schema
38+
let sql = loadSqlFile('employee.sql');
39+
40+
// Replace .read directives with the actual file contents
41+
// This is necessary because in-memory SQLite can't use .read
42+
const readRegex = /\.read\s+([a-zA-Z0-9_]+\.sql)/g;
43+
let match;
44+
45+
while ((match = readRegex.exec(sql)) !== null) {
46+
const includePath = match[1];
47+
const includeContent = loadSqlFile(includePath);
48+
49+
// Replace the .read line with the file contents
50+
sql = sql.replace(match[0], includeContent);
51+
}
52+
53+
return sql;
54+
}

src/config/env.ts

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,34 @@ export function loadEnvFiles(): string | null {
7171
return null;
7272
}
7373

74+
/**
75+
* Check if demo mode is enabled from command line args
76+
* Returns true if --demo flag is provided
77+
*/
78+
export function isDemoMode(): boolean {
79+
const args = parseCommandLineArgs();
80+
return args.demo === 'true';
81+
}
82+
7483
/**
7584
* Resolve DSN from command line args, environment variables, or .env files
7685
* Returns the DSN and its source, or null if not found
7786
*/
78-
export function resolveDSN(): { dsn: string; source: string } | null {
87+
export function resolveDSN(): { dsn: string; source: string; isDemo?: boolean } | null {
7988
// Get command line arguments
8089
const args = parseCommandLineArgs();
8190

82-
// 1. Check command line arguments first (highest priority)
91+
// Check for demo mode first (highest priority)
92+
if (isDemoMode()) {
93+
// Will use in-memory SQLite with demo data
94+
return {
95+
dsn: 'sqlite::memory:',
96+
source: 'demo mode',
97+
isDemo: true
98+
};
99+
}
100+
101+
// 1. Check command line arguments
83102
if (args.dsn) {
84103
return { dsn: args.dsn, source: 'command line argument' };
85104
}

src/connectors/interface.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,8 @@ export interface Connector {
4949
/** DSN parser for this connector */
5050
dsnParser: DSNParser;
5151

52-
/** Connect to the database using DSN */
53-
connect(dsn: string): Promise<void>;
52+
/** Connect to the database using DSN, with optional init script */
53+
connect(dsn: string, initScript?: string): Promise<void>;
5454

5555
/** Close the connection */
5656
disconnect(): Promise<void>;

src/connectors/manager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export class ConnectorManager {
1919
/**
2020
* Initialize and connect to the database using a DSN
2121
*/
22-
async connectWithDSN(dsn: string): Promise<void> {
22+
async connectWithDSN(dsn: string, initScript?: string): Promise<void> {
2323
// First try to find a connector that can handle this DSN
2424
let connector = ConnectorRegistry.getConnectorForDSN(dsn);
2525

@@ -30,7 +30,7 @@ export class ConnectorManager {
3030
this.activeConnector = connector;
3131

3232
// Connect to the database
33-
await this.activeConnector.connect(dsn);
33+
await this.activeConnector.connect(dsn, initScript);
3434
this.connected = true;
3535
}
3636

src/connectors/sqlite/index.ts

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ export class SQLiteConnector implements Connector {
8484
private db: sqlite3.Database | null = null;
8585
private dbPath: string = ':memory:'; // Default to in-memory database
8686

87-
async connect(dsn: string): Promise<void> {
87+
async connect(dsn: string, initScript?: string): Promise<void> {
8888
const config = this.dsnParser.parse(dsn);
8989
this.dbPath = config.dbPath;
9090

@@ -96,7 +96,21 @@ export class SQLiteConnector implements Connector {
9696
} else {
9797
// Can't use console.log here because it will break the stdio transport
9898
console.error("Successfully connected to SQLite database");
99-
resolve();
99+
100+
// If an initialization script is provided, run it
101+
if (initScript) {
102+
this.db!.exec(initScript, (err) => {
103+
if (err) {
104+
console.error("Failed to initialize database with script:", err);
105+
reject(err);
106+
} else {
107+
console.error("Successfully initialized database with script");
108+
resolve();
109+
}
110+
});
111+
} else {
112+
resolve();
113+
}
100114
}
101115
});
102116
});

src/server.ts

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ import { fileURLToPath } from 'url';
88

99
import { ConnectorManager } from './connectors/manager.js';
1010
import { ConnectorRegistry } from './connectors/interface.js';
11-
import { resolveDSN, resolveTransport, resolvePort } from './config/env.js';
11+
import { resolveDSN, resolveTransport, resolvePort, isDemoMode } from './config/env.js';
12+
import { getSqliteInMemorySetupSql } from './config/demo-loader.js';
1213
import { registerResources } from './resources/index.js';
1314
import { registerTools } from './tools/index.js';
1415
import { registerPrompts } from './prompts/index.js';
@@ -28,7 +29,9 @@ export const SERVER_VERSION = packageJson.version;
2829
/**
2930
* Generate ASCII art banner with version information
3031
*/
31-
export function generateBanner(version: string): string {
32+
export function generateBanner(version: string, isDemo: boolean = false): string {
33+
const demoText = isDemo ? " [DEMO MODE]" : "";
34+
3235
return `
3336
_____ ____ _ _ _
3437
| __ \\| _ \\| | | | | |
@@ -37,7 +40,7 @@ export function generateBanner(version: string): string {
3740
| |__| | |_) | | | | |_| | |_) |
3841
|_____/|____/|_| |_|\\__,_|_.__/
3942
40-
v${version} - Universal Database MCP Server
43+
v${version}${demoText} - Universal Database MCP Server
4144
`;
4245
}
4346

@@ -59,9 +62,10 @@ export async function main(): Promise<void> {
5962
ERROR: Database connection string (DSN) is required.
6063
Please provide the DSN in one of these ways (in order of priority):
6164
62-
1. Command line argument: --dsn="your-connection-string"
63-
2. Environment variable: export DSN="your-connection-string"
64-
3. .env file: DSN=your-connection-string
65+
1. Use demo mode: --demo (uses in-memory SQLite with sample employee database)
66+
2. Command line argument: --dsn="your-connection-string"
67+
3. Environment variable: export DSN="your-connection-string"
68+
4. .env file: DSN=your-connection-string
6569
6670
Example formats:
6771
${sampleFormats}
@@ -86,15 +90,23 @@ See documentation for more details on configuring database connections.
8690
const connectorManager = new ConnectorManager();
8791
console.error(`Connecting with DSN: ${dsnData.dsn}`);
8892
console.error(`DSN source: ${dsnData.source}`);
89-
await connectorManager.connectWithDSN(dsnData.dsn);
93+
94+
// If in demo mode, load the employee database
95+
if (dsnData.isDemo) {
96+
console.error('Running in demo mode with sample employee database');
97+
const initScript = getSqliteInMemorySetupSql();
98+
await connectorManager.connectWithDSN(dsnData.dsn, initScript);
99+
} else {
100+
await connectorManager.connectWithDSN(dsnData.dsn);
101+
}
90102

91103
// Resolve transport type
92104
const transportData = resolveTransport();
93105
console.error(`Using transport: ${transportData.type}`);
94106
console.error(`Transport source: ${transportData.source}`);
95107

96108
// Print ASCII art banner with version and slogan
97-
console.error(generateBanner(SERVER_VERSION));
109+
console.error(generateBanner(SERVER_VERSION, dsnData.isDemo));
98110

99111
// Set up transport based on type
100112
if (transportData.type === 'sse') {

0 commit comments

Comments
 (0)