mirror of
https://github.com/executeautomation/mcp-database-server.git
synced 2025-12-09 21:12:57 +08:00
Added support for SQL Server
This commit is contained in:
0
data/test.db
Normal file
0
data/test.db
Normal file
155
docs/sql-server-setup.md
Normal file
155
docs/sql-server-setup.md
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# SQL Server Setup Guide
|
||||||
|
|
||||||
|
This guide provides instructions for setting up and using the SQL Server adapter with the MCP Database Server.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
1. Access to a SQL Server instance (2012 or later)
|
||||||
|
2. Node.js 18 or later
|
||||||
|
3. Required permissions to connect to the SQL Server database
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Follow the main installation steps in the README.md file
|
||||||
|
2. Ensure the mssql package is installed:
|
||||||
|
```bash
|
||||||
|
npm install mssql
|
||||||
|
npm install @types/mssql --save-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication Options
|
||||||
|
|
||||||
|
The SQL Server adapter supports multiple authentication methods:
|
||||||
|
|
||||||
|
### SQL Server Authentication
|
||||||
|
|
||||||
|
Use the `--user` and `--password` parameters to authenticate with SQL Server credentials:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node dist/src/index.js --sqlserver --server myserver --database mydatabase --user myuser --password mypassword
|
||||||
|
```
|
||||||
|
|
||||||
|
### Windows Authentication
|
||||||
|
|
||||||
|
Omit the `--user` and `--password` parameters to use Windows Authentication (trusted connection):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
node dist/src/index.js --sqlserver --server myserver --database mydatabase
|
||||||
|
```
|
||||||
|
|
||||||
|
### Azure Active Directory
|
||||||
|
|
||||||
|
For Azure SQL Database with Azure AD authentication, you'll need to set up connection options:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"sqlserver": {
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"/path/to/mcp-database-server/dist/src/index.js",
|
||||||
|
"--sqlserver",
|
||||||
|
"--server", "myserver.database.windows.net",
|
||||||
|
"--database", "mydatabase",
|
||||||
|
"--user", "myuser@mydomain.com",
|
||||||
|
"--password", "mypassword"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuring Claude
|
||||||
|
|
||||||
|
Update your Claude configuration file to add SQL Server support:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"sqlserver": {
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"/path/to/mcp-database-server/dist/src/index.js",
|
||||||
|
"--sqlserver",
|
||||||
|
"--server", "your-server-name",
|
||||||
|
"--database", "your-database-name",
|
||||||
|
"--user", "your-username",
|
||||||
|
"--password", "your-password"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
For local SQL Server with Windows Authentication:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"sqlserver": {
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"/path/to/mcp-database-server/dist/src/index.js",
|
||||||
|
"--sqlserver",
|
||||||
|
"--server", "localhost\\SQLEXPRESS",
|
||||||
|
"--database", "your-database-name"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Connection Options
|
||||||
|
|
||||||
|
Additional connection options include:
|
||||||
|
|
||||||
|
- `--port`: Specify a non-default port (default is 1433)
|
||||||
|
- Add `--trustServerCertificate true` if you're connecting to a development/test server with a self-signed certificate
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Common Connection Issues
|
||||||
|
|
||||||
|
1. **Login failed for user**
|
||||||
|
- Verify username and password
|
||||||
|
- Check if the SQL Server account is enabled and not locked
|
||||||
|
|
||||||
|
2. **Cannot connect to server**
|
||||||
|
- Ensure SQL Server is running
|
||||||
|
- Check firewall settings
|
||||||
|
- Verify server name is correct (including instance name if applicable)
|
||||||
|
|
||||||
|
3. **SSL errors**
|
||||||
|
- Add `--trustServerCertificate true` for development environments
|
||||||
|
|
||||||
|
### Verifying Connection
|
||||||
|
|
||||||
|
You can test your SQL Server connection using the standard SQL Server tools:
|
||||||
|
|
||||||
|
1. Using SQL Server Management Studio (SSMS)
|
||||||
|
2. Using the `sqlcmd` utility:
|
||||||
|
```
|
||||||
|
sqlcmd -S server_name -d database_name -U username -P password
|
||||||
|
```
|
||||||
|
|
||||||
|
## SQL Syntax Differences
|
||||||
|
|
||||||
|
Note that there may be syntax differences between SQLite and SQL Server. Here are some common differences:
|
||||||
|
|
||||||
|
1. **String concatenation**
|
||||||
|
- SQLite: `||`
|
||||||
|
- SQL Server: `+`
|
||||||
|
|
||||||
|
2. **Limit/Offset**
|
||||||
|
- SQLite: `LIMIT x OFFSET y`
|
||||||
|
- SQL Server: `OFFSET y ROWS FETCH NEXT x ROWS ONLY`
|
||||||
|
|
||||||
|
3. **Date formatting**
|
||||||
|
- SQLite: `strftime()`
|
||||||
|
- SQL Server: `FORMAT()` or `CONVERT()`
|
||||||
|
|
||||||
|
4. **Auto-increment columns**
|
||||||
|
- SQLite: `INTEGER PRIMARY KEY AUTOINCREMENT`
|
||||||
|
- SQL Server: `INT IDENTITY(1,1)`
|
||||||
|
|
||||||
|
When using Claude, be aware of these syntax differences when crafting SQL queries.
|
||||||
1575
package-lock.json
generated
1575
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@executeautomation/database-server",
|
"name": "@executeautomation/database-server",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "MCP server for interacting with SQLite databases by ExecuteAutomation",
|
"description": "MCP server for interacting with SQLite and SQL Server databases by ExecuteAutomation",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "ExecuteAutomation (https://executeautomation.com)",
|
"author": "ExecuteAutomation (https://executeautomation.com)",
|
||||||
"homepage": "https://executeautomation.com",
|
"homepage": "https://executeautomation.com",
|
||||||
@@ -24,12 +24,14 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@modelcontextprotocol/sdk": "1.9.0",
|
"@modelcontextprotocol/sdk": "1.9.0",
|
||||||
|
"mssql": "11.0.1",
|
||||||
"sqlite3": "5.1.7"
|
"sqlite3": "5.1.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/mssql": "^9.1.5",
|
||||||
"@types/sqlite3": "5.1.0",
|
"@types/sqlite3": "5.1.0",
|
||||||
"rimraf": "^5.0.5",
|
"rimraf": "^5.0.5",
|
||||||
"shx": "0.4.0",
|
"shx": "0.4.0",
|
||||||
"typescript": "5.8.3"
|
"typescript": "5.8.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
279
readme.md
279
readme.md
@@ -1,202 +1,115 @@
|
|||||||
# ExecuteAutomation Database Server
|
# MCP Database Server
|
||||||
|
|
||||||
A Model Context Protocol server that provides access to SQLite databases. This server enables LLMs like Claude to inspect database schemas and execute SQL queries.
|
This MCP (Model Context Protocol) server provides database access capabilities to Claude, supporting both SQLite and SQL Server databases.
|
||||||
|
|
||||||
## Components
|
## Installation
|
||||||
|
|
||||||
### Tools
|
1. Clone the repository:
|
||||||
|
```
|
||||||
The server offers nine core tools:
|
git clone https://github.com/executeautomation/database-server.git
|
||||||
|
cd database-server
|
||||||
#### Query Tools
|
|
||||||
- **read_query**
|
|
||||||
- Execute SELECT queries to read data from the database
|
|
||||||
- Input:
|
|
||||||
- `query` (string): The SELECT SQL query to execute
|
|
||||||
- Returns: Query results as array of objects
|
|
||||||
|
|
||||||
- **write_query**
|
|
||||||
- Execute INSERT, UPDATE, or DELETE queries
|
|
||||||
- Input:
|
|
||||||
- `query` (string): The SQL modification query
|
|
||||||
- Returns: `{ affected_rows: number }`
|
|
||||||
|
|
||||||
#### Schema Management Tools
|
|
||||||
- **create_table**
|
|
||||||
- Create new tables in the database
|
|
||||||
- Input:
|
|
||||||
- `query` (string): CREATE TABLE SQL statement
|
|
||||||
- Returns: Confirmation of table creation
|
|
||||||
|
|
||||||
- **alter_table**
|
|
||||||
- Modify existing table schema (add columns, rename tables, etc.)
|
|
||||||
- Input:
|
|
||||||
- `query` (string): ALTER TABLE SQL statement
|
|
||||||
- Returns: Confirmation of table alteration
|
|
||||||
|
|
||||||
- **drop_table**
|
|
||||||
- Remove a table from the database with safety confirmation
|
|
||||||
- Input:
|
|
||||||
- `table_name` (string): Name of the table to drop
|
|
||||||
- `confirm` (boolean): Safety confirmation flag (must be true to actually drop)
|
|
||||||
- Returns: Confirmation message or safety warning
|
|
||||||
|
|
||||||
#### Schema Information Tools
|
|
||||||
- **list_tables**
|
|
||||||
- Get a list of all tables in the database
|
|
||||||
- No input required
|
|
||||||
- Returns: Array of table names
|
|
||||||
|
|
||||||
- **describe_table**
|
|
||||||
- View schema information for a specific table
|
|
||||||
- Input:
|
|
||||||
- `table_name` (string): Name of table to describe
|
|
||||||
- Returns: Array of column definitions with names and types
|
|
||||||
|
|
||||||
#### Data Export Tools
|
|
||||||
- **export_query**
|
|
||||||
- Export query results to various formats
|
|
||||||
- Input:
|
|
||||||
- `query` (string): The SELECT SQL query to execute
|
|
||||||
- `format` (string): Output format - either "csv" or "json"
|
|
||||||
- Returns: Query results formatted as CSV or JSON
|
|
||||||
|
|
||||||
#### Insights
|
|
||||||
- **append_insight**
|
|
||||||
- Add a business insight to the memo
|
|
||||||
- Input:
|
|
||||||
- `insight` (string): Business insight discovered from data analysis
|
|
||||||
- Returns: Confirmation message
|
|
||||||
|
|
||||||
### Resources
|
|
||||||
|
|
||||||
The server provides schema information for each table in the database:
|
|
||||||
|
|
||||||
- **Table Schemas** (`sqlite:///<path>/<table>/schema`)
|
|
||||||
- JSON schema information for each table
|
|
||||||
- Includes column names and data types
|
|
||||||
- Automatically discovered from database metadata
|
|
||||||
|
|
||||||
## Usage with Claude Desktop
|
|
||||||
|
|
||||||
To use this server with the Claude Desktop app, add the following configuration to the "mcpServers" section of your `claude_desktop_config.json`:
|
|
||||||
|
|
||||||
### Docker
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"database": {
|
|
||||||
"command": "docker",
|
|
||||||
"args": [
|
|
||||||
"run",
|
|
||||||
"-i",
|
|
||||||
"--rm",
|
|
||||||
"executeautomation/database-server",
|
|
||||||
"/path/to/database.db"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### NPX (Published Package)
|
2. Install dependencies:
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"database": {
|
|
||||||
"command": "npx",
|
|
||||||
"args": [
|
|
||||||
"-y",
|
|
||||||
"@executeautomation/database-server",
|
|
||||||
"/path/to/database.db"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Local Development
|
|
||||||
|
|
||||||
For local development and debugging, you can point directly to your compiled JavaScript file:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"database": {
|
|
||||||
"command": "node",
|
|
||||||
"args": [
|
|
||||||
"/path/to/your/project/dist/index.js",
|
|
||||||
"/path/to/your/database.db"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Example with full paths:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"mcpServers": {
|
|
||||||
"database": {
|
|
||||||
"command": "node",
|
|
||||||
"args": [
|
|
||||||
"/Users/username/projects/database-server/dist/index.js",
|
|
||||||
"/Users/username/databases/mydata.db"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
The Claude Desktop config file is typically located at:
|
|
||||||
- macOS: `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
||||||
- Windows: `%APPDATA%\Claude\claude_desktop_config.json`
|
|
||||||
- Linux: `~/.config/Claude/claude_desktop_config.json`
|
|
||||||
|
|
||||||
Replace all paths with your actual file paths.
|
|
||||||
|
|
||||||
## Building and Development
|
|
||||||
|
|
||||||
### Quick Build
|
|
||||||
|
|
||||||
Use the included build script to install dependencies and build the project:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
chmod +x build.sh
|
|
||||||
./build.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
### Manual Build Steps
|
|
||||||
|
|
||||||
```sh
|
|
||||||
# Install dependencies
|
|
||||||
npm install
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
# Build the TypeScript
|
3. Build the project:
|
||||||
|
```
|
||||||
npm run build
|
npm run build
|
||||||
```
|
```
|
||||||
|
|
||||||
### Docker Build
|
## Usage
|
||||||
|
|
||||||
```sh
|
### SQLite Database
|
||||||
docker build -t executeautomation/database-server -f Dockerfile .
|
|
||||||
|
To use with an SQLite database:
|
||||||
|
|
||||||
|
```
|
||||||
|
node dist/src/index.js /path/to/your/database.db
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
### SQL Server Database
|
||||||
|
|
||||||
If you encounter connection issues:
|
To use with a SQL Server database:
|
||||||
|
|
||||||
1. Make sure the database file path is correct and accessible
|
```
|
||||||
2. Verify that the compiled JavaScript file exists at the specified path
|
node dist/src/index.js --sqlserver --server <server-name> --database <database-name> [--user <username> --password <password>]
|
||||||
3. Check the Claude Desktop logs for detailed error messages
|
```
|
||||||
4. Restart Claude Desktop after making configuration changes
|
|
||||||
|
Required parameters:
|
||||||
|
- `--server`: SQL Server host name or IP address
|
||||||
|
- `--database`: Name of the database
|
||||||
|
|
||||||
|
Optional parameters:
|
||||||
|
- `--user`: Username for SQL Server authentication (if not provided, Windows Authentication will be used)
|
||||||
|
- `--password`: Password for SQL Server authentication
|
||||||
|
- `--port`: Port number (default: 1433)
|
||||||
|
|
||||||
|
## Configuring Claude
|
||||||
|
|
||||||
|
Update your Claude configuration file to add the MCP Database Server:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"sqlite": {
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"/path/to/mcp-database-server/dist/src/index.js",
|
||||||
|
"/path/to/your/database.db"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"sqlserver": {
|
||||||
|
"command": "node",
|
||||||
|
"args": [
|
||||||
|
"/path/to/mcp-database-server/dist/src/index.js",
|
||||||
|
"--sqlserver",
|
||||||
|
"--server", "your-server-name",
|
||||||
|
"--database", "your-database-name",
|
||||||
|
"--user", "your-username",
|
||||||
|
"--password", "your-password"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Available Tools
|
||||||
|
|
||||||
|
The MCP Database Server provides the following tools:
|
||||||
|
|
||||||
|
- `read_query`: Execute SELECT queries to read data from the database
|
||||||
|
- `write_query`: Execute INSERT, UPDATE, or DELETE queries
|
||||||
|
- `create_table`: Create new tables in the database
|
||||||
|
- `alter_table`: Modify existing table schema (add columns, rename tables, etc.)
|
||||||
|
- `drop_table`: Remove a table from the database with safety confirmation
|
||||||
|
- `export_query`: Export query results to various formats (CSV, JSON)
|
||||||
|
- `list_tables`: Get a list of all tables in the database
|
||||||
|
- `describe_table`: View schema information for a specific table
|
||||||
|
- `append_insight`: Add a business insight to the memo
|
||||||
|
- `list_insights`: List all business insights in the memo
|
||||||
|
|
||||||
|
## Development
|
||||||
|
|
||||||
|
To run the server in development mode:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
To watch for changes during development:
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run watch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Node.js 18+
|
||||||
|
- For SQL Server connectivity: SQL Server 2012 or later
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
This MCP server is licensed under the MIT License. This means you are free to use, modify, and distribute the software, subject to the terms and conditions of the MIT License.
|
MIT
|
||||||
|
|
||||||
## About ExecuteAutomation
|
|
||||||
|
|
||||||
This server is maintained by ExecuteAutomation. Visit [executeautomation.com](https://executeautomation.com) for more tools and resources.
|
|
||||||
|
|||||||
BIN
src/.DS_Store
vendored
BIN
src/.DS_Store
vendored
Binary file not shown.
74
src/db/adapter.ts
Normal file
74
src/db/adapter.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
/**
|
||||||
|
* Database adapter interface
|
||||||
|
* Defines the contract for all database implementations (SQLite, SQL Server)
|
||||||
|
*/
|
||||||
|
export interface DbAdapter {
|
||||||
|
/**
|
||||||
|
* Initialize database connection
|
||||||
|
*/
|
||||||
|
init(): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close database connection
|
||||||
|
*/
|
||||||
|
close(): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a query and return all results
|
||||||
|
* @param query SQL query to execute
|
||||||
|
* @param params Query parameters
|
||||||
|
*/
|
||||||
|
all(query: string, params?: any[]): Promise<any[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a query that modifies data
|
||||||
|
* @param query SQL query to execute
|
||||||
|
* @param params Query parameters
|
||||||
|
*/
|
||||||
|
run(query: string, params?: any[]): Promise<{ changes: number, lastID: number }>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute multiple SQL statements
|
||||||
|
* @param query SQL statements to execute
|
||||||
|
*/
|
||||||
|
exec(query: string): Promise<void>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database metadata
|
||||||
|
*/
|
||||||
|
getMetadata(): { name: string, type: string, path?: string, server?: string, database?: string };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for listing tables
|
||||||
|
*/
|
||||||
|
getListTablesQuery(): string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for describing a table
|
||||||
|
* @param tableName Table name
|
||||||
|
*/
|
||||||
|
getDescribeTableQuery(tableName: string): string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Import adapters using dynamic imports
|
||||||
|
import { SqliteAdapter } from './sqlite-adapter.js';
|
||||||
|
import { SqlServerAdapter } from './sqlserver-adapter.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Factory function to create the appropriate database adapter
|
||||||
|
*/
|
||||||
|
export function createDbAdapter(type: string, connectionInfo: any): DbAdapter {
|
||||||
|
switch (type.toLowerCase()) {
|
||||||
|
case 'sqlite':
|
||||||
|
// For SQLite, if connectionInfo is a string, use it directly as path
|
||||||
|
if (typeof connectionInfo === 'string') {
|
||||||
|
return new SqliteAdapter(connectionInfo);
|
||||||
|
} else {
|
||||||
|
return new SqliteAdapter(connectionInfo.path);
|
||||||
|
}
|
||||||
|
case 'sqlserver':
|
||||||
|
return new SqlServerAdapter(connectionInfo);
|
||||||
|
default:
|
||||||
|
throw new Error(`Unsupported database type: ${type}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
114
src/db/index.ts
114
src/db/index.ts
@@ -1,23 +1,28 @@
|
|||||||
import sqlite3 from "sqlite3";
|
import { DbAdapter, createDbAdapter } from './adapter.js';
|
||||||
|
|
||||||
let db: sqlite3.Database;
|
// Store the active database adapter
|
||||||
let databasePath: string;
|
let dbAdapter: DbAdapter | null = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Initialize the SQLite database connection
|
* Initialize the database connection
|
||||||
* @param dbPath Path to the SQLite database file
|
* @param connectionInfo Connection information object or SQLite path string
|
||||||
|
* @param dbType Database type ('sqlite' or 'sqlserver')
|
||||||
*/
|
*/
|
||||||
export function initDatabase(dbPath: string): Promise<void> {
|
export async function initDatabase(connectionInfo: any, dbType: string = 'sqlite'): Promise<void> {
|
||||||
databasePath = dbPath;
|
try {
|
||||||
return new Promise((resolve, reject) => {
|
// If connectionInfo is a string, assume it's a SQLite path
|
||||||
db = new sqlite3.Database(dbPath, (err) => {
|
if (typeof connectionInfo === 'string') {
|
||||||
if (err) {
|
connectionInfo = { path: connectionInfo };
|
||||||
reject(err);
|
}
|
||||||
} else {
|
|
||||||
resolve();
|
// Create appropriate adapter based on database type
|
||||||
|
dbAdapter = createDbAdapter(dbType, connectionInfo);
|
||||||
|
|
||||||
|
// Initialize the connection
|
||||||
|
await dbAdapter.init();
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Failed to initialize database: ${(error as Error).message}`);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,15 +32,10 @@ export function initDatabase(dbPath: string): Promise<void> {
|
|||||||
* @returns Promise with query results
|
* @returns Promise with query results
|
||||||
*/
|
*/
|
||||||
export function dbAll(query: string, params: any[] = []): Promise<any[]> {
|
export function dbAll(query: string, params: any[] = []): Promise<any[]> {
|
||||||
return new Promise((resolve, reject) => {
|
if (!dbAdapter) {
|
||||||
db.all(query, params, (err: Error | null, rows: any[]) => {
|
throw new Error("Database not initialized");
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve(rows);
|
|
||||||
}
|
}
|
||||||
});
|
return dbAdapter.all(query, params);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -45,15 +45,10 @@ export function dbAll(query: string, params: any[] = []): Promise<any[]> {
|
|||||||
* @returns Promise with result info
|
* @returns Promise with result info
|
||||||
*/
|
*/
|
||||||
export function dbRun(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
export function dbRun(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
||||||
return new Promise((resolve, reject) => {
|
if (!dbAdapter) {
|
||||||
db.run(query, params, function(this: sqlite3.RunResult, err: Error | null) {
|
throw new Error("Database not initialized");
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve({ changes: this.changes, lastID: this.lastID });
|
|
||||||
}
|
}
|
||||||
});
|
return dbAdapter.run(query, params);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -62,40 +57,49 @@ export function dbRun(query: string, params: any[] = []): Promise<{ changes: num
|
|||||||
* @returns Promise that resolves when execution completes
|
* @returns Promise that resolves when execution completes
|
||||||
*/
|
*/
|
||||||
export function dbExec(query: string): Promise<void> {
|
export function dbExec(query: string): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
if (!dbAdapter) {
|
||||||
db.exec(query, (err: Error | null) => {
|
throw new Error("Database not initialized");
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
}
|
||||||
});
|
return dbAdapter.exec(query);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Close the database connection
|
* Close the database connection
|
||||||
*/
|
*/
|
||||||
export function closeDatabase(): Promise<void> {
|
export function closeDatabase(): Promise<void> {
|
||||||
return new Promise((resolve, reject) => {
|
if (!dbAdapter) {
|
||||||
if (!db) {
|
return Promise.resolve();
|
||||||
resolve();
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
return dbAdapter.close();
|
||||||
db.close((err: Error | null) => {
|
|
||||||
if (err) {
|
|
||||||
reject(err);
|
|
||||||
} else {
|
|
||||||
resolve();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the current database path
|
* Get database metadata
|
||||||
*/
|
*/
|
||||||
export function getDatabasePath(): string {
|
export function getDatabaseMetadata(): { name: string, type: string, path?: string, server?: string, database?: string } {
|
||||||
return databasePath;
|
if (!dbAdapter) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
return dbAdapter.getMetadata();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for listing tables
|
||||||
|
*/
|
||||||
|
export function getListTablesQuery(): string {
|
||||||
|
if (!dbAdapter) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
return dbAdapter.getListTablesQuery();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for describing a table
|
||||||
|
* @param tableName Table name
|
||||||
|
*/
|
||||||
|
export function getDescribeTableQuery(tableName: string): string {
|
||||||
|
if (!dbAdapter) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
return dbAdapter.getDescribeTableQuery(tableName);
|
||||||
}
|
}
|
||||||
145
src/db/sqlite-adapter.ts
Normal file
145
src/db/sqlite-adapter.ts
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import sqlite3 from "sqlite3";
|
||||||
|
import { DbAdapter } from "./adapter.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQLite database adapter implementation
|
||||||
|
*/
|
||||||
|
export class SqliteAdapter implements DbAdapter {
|
||||||
|
private db: sqlite3.Database | null = null;
|
||||||
|
private dbPath: string;
|
||||||
|
|
||||||
|
constructor(dbPath: string) {
|
||||||
|
this.dbPath = dbPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize the SQLite database connection
|
||||||
|
*/
|
||||||
|
async init(): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
// Ensure the dbPath is accessible
|
||||||
|
console.log(`Opening SQLite database at: ${this.dbPath}`);
|
||||||
|
this.db = new sqlite3.Database(this.dbPath, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, (err) => {
|
||||||
|
if (err) {
|
||||||
|
console.error(`SQLite connection error: ${err.message}`);
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
console.log("SQLite database opened successfully");
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a SQL query and get all results
|
||||||
|
* @param query SQL query to execute
|
||||||
|
* @param params Query parameters
|
||||||
|
* @returns Promise with query results
|
||||||
|
*/
|
||||||
|
async all(query: string, params: any[] = []): Promise<any[]> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.db!.all(query, params, (err: Error | null, rows: any[]) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve(rows);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a SQL query that modifies data
|
||||||
|
* @param query SQL query to execute
|
||||||
|
* @param params Query parameters
|
||||||
|
* @returns Promise with result info
|
||||||
|
*/
|
||||||
|
async run(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.db!.run(query, params, function(this: sqlite3.RunResult, err: Error | null) {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve({ changes: this.changes, lastID: this.lastID });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute multiple SQL statements
|
||||||
|
* @param query SQL statements to execute
|
||||||
|
* @returns Promise that resolves when execution completes
|
||||||
|
*/
|
||||||
|
async exec(query: string): Promise<void> {
|
||||||
|
if (!this.db) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
this.db!.exec(query, (err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the database connection
|
||||||
|
*/
|
||||||
|
async close(): Promise<void> {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
if (!this.db) {
|
||||||
|
resolve();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.db.close((err: Error | null) => {
|
||||||
|
if (err) {
|
||||||
|
reject(err);
|
||||||
|
} else {
|
||||||
|
this.db = null;
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database metadata
|
||||||
|
*/
|
||||||
|
getMetadata(): { name: string, type: string, path: string } {
|
||||||
|
return {
|
||||||
|
name: "SQLite",
|
||||||
|
type: "sqlite",
|
||||||
|
path: this.dbPath
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for listing tables
|
||||||
|
*/
|
||||||
|
getListTablesQuery(): string {
|
||||||
|
return "SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for describing a table
|
||||||
|
* @param tableName Table name
|
||||||
|
*/
|
||||||
|
getDescribeTableQuery(tableName: string): string {
|
||||||
|
return `PRAGMA table_info(${tableName})`;
|
||||||
|
}
|
||||||
|
}
|
||||||
212
src/db/sqlserver-adapter.ts
Normal file
212
src/db/sqlserver-adapter.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
import { DbAdapter } from "./adapter.js";
|
||||||
|
import sql from 'mssql';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SQL Server database adapter implementation
|
||||||
|
*/
|
||||||
|
export class SqlServerAdapter implements DbAdapter {
|
||||||
|
private pool: sql.ConnectionPool | null = null;
|
||||||
|
private config: sql.config;
|
||||||
|
private server: string;
|
||||||
|
private database: string;
|
||||||
|
|
||||||
|
constructor(connectionInfo: {
|
||||||
|
server: string;
|
||||||
|
database: string;
|
||||||
|
user?: string;
|
||||||
|
password?: string;
|
||||||
|
port?: number;
|
||||||
|
trustServerCertificate?: boolean;
|
||||||
|
options?: any;
|
||||||
|
}) {
|
||||||
|
this.server = connectionInfo.server;
|
||||||
|
this.database = connectionInfo.database;
|
||||||
|
|
||||||
|
// Create SQL Server connection config
|
||||||
|
this.config = {
|
||||||
|
server: connectionInfo.server,
|
||||||
|
database: connectionInfo.database,
|
||||||
|
port: connectionInfo.port || 1433,
|
||||||
|
options: {
|
||||||
|
trustServerCertificate: connectionInfo.trustServerCertificate ?? true,
|
||||||
|
...connectionInfo.options
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add authentication options
|
||||||
|
if (connectionInfo.user && connectionInfo.password) {
|
||||||
|
this.config.user = connectionInfo.user;
|
||||||
|
this.config.password = connectionInfo.password;
|
||||||
|
} else {
|
||||||
|
// Use Windows authentication if no username/password provided
|
||||||
|
this.config.options!.trustedConnection = true;
|
||||||
|
this.config.options!.enableArithAbort = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize SQL Server connection
|
||||||
|
*/
|
||||||
|
async init(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.pool = await new sql.ConnectionPool(this.config).connect();
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`Failed to connect to SQL Server: ${(err as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a SQL query and get all results
|
||||||
|
* @param query SQL query to execute
|
||||||
|
* @param params Query parameters
|
||||||
|
* @returns Promise with query results
|
||||||
|
*/
|
||||||
|
async all(query: string, params: any[] = []): Promise<any[]> {
|
||||||
|
if (!this.pool) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = this.pool.request();
|
||||||
|
|
||||||
|
// Add parameters to the request
|
||||||
|
params.forEach((param, index) => {
|
||||||
|
request.input(`param${index}`, param);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace ? with named parameters
|
||||||
|
const preparedQuery = query.replace(/\?/g, (_, i) => `@param${i}`);
|
||||||
|
|
||||||
|
const result = await request.query(preparedQuery);
|
||||||
|
return result.recordset;
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`SQL Server query error: ${(err as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute a SQL query that modifies data
|
||||||
|
* @param query SQL query to execute
|
||||||
|
* @param params Query parameters
|
||||||
|
* @returns Promise with result info
|
||||||
|
*/
|
||||||
|
async run(query: string, params: any[] = []): Promise<{ changes: number, lastID: number }> {
|
||||||
|
if (!this.pool) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = this.pool.request();
|
||||||
|
|
||||||
|
// Add parameters to the request
|
||||||
|
params.forEach((param, index) => {
|
||||||
|
request.input(`param${index}`, param);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Replace ? with named parameters
|
||||||
|
const preparedQuery = query.replace(/\?/g, (_, i) => `@param${i}`);
|
||||||
|
|
||||||
|
// Add output parameter for identity value if it's an INSERT
|
||||||
|
let lastID = 0;
|
||||||
|
if (query.trim().toUpperCase().startsWith('INSERT')) {
|
||||||
|
request.output('insertedId', sql.Int, 0);
|
||||||
|
const updatedQuery = `${preparedQuery}; SELECT @insertedId = SCOPE_IDENTITY();`;
|
||||||
|
const result = await request.query(updatedQuery);
|
||||||
|
lastID = result.output.insertedId || 0;
|
||||||
|
} else {
|
||||||
|
const result = await request.query(preparedQuery);
|
||||||
|
lastID = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
changes: this.getAffectedRows(query, lastID),
|
||||||
|
lastID: lastID
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`SQL Server query error: ${(err as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute multiple SQL statements
|
||||||
|
* @param query SQL statements to execute
|
||||||
|
* @returns Promise that resolves when execution completes
|
||||||
|
*/
|
||||||
|
async exec(query: string): Promise<void> {
|
||||||
|
if (!this.pool) {
|
||||||
|
throw new Error("Database not initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const request = this.pool.request();
|
||||||
|
await request.batch(query);
|
||||||
|
} catch (err) {
|
||||||
|
throw new Error(`SQL Server batch error: ${(err as Error).message}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Close the database connection
|
||||||
|
*/
|
||||||
|
async close(): Promise<void> {
|
||||||
|
if (this.pool) {
|
||||||
|
await this.pool.close();
|
||||||
|
this.pool = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database metadata
|
||||||
|
*/
|
||||||
|
getMetadata(): { name: string, type: string, server: string, database: string } {
|
||||||
|
return {
|
||||||
|
name: "SQL Server",
|
||||||
|
type: "sqlserver",
|
||||||
|
server: this.server,
|
||||||
|
database: this.database
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for listing tables
|
||||||
|
*/
|
||||||
|
getListTablesQuery(): string {
|
||||||
|
return "SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE' ORDER BY TABLE_NAME";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get database-specific query for describing a table
|
||||||
|
* @param tableName Table name
|
||||||
|
*/
|
||||||
|
getDescribeTableQuery(tableName: string): string {
|
||||||
|
return `
|
||||||
|
SELECT
|
||||||
|
c.COLUMN_NAME as name,
|
||||||
|
c.DATA_TYPE as type,
|
||||||
|
CASE WHEN c.IS_NULLABLE = 'YES' THEN 1 ELSE 0 END as notnull,
|
||||||
|
CASE WHEN pk.CONSTRAINT_TYPE = 'PRIMARY KEY' THEN 1 ELSE 0 END as pk,
|
||||||
|
c.COLUMN_DEFAULT as dflt_value
|
||||||
|
FROM
|
||||||
|
INFORMATION_SCHEMA.COLUMNS c
|
||||||
|
LEFT JOIN
|
||||||
|
INFORMATION_SCHEMA.KEY_COLUMN_USAGE kcu ON c.TABLE_NAME = kcu.TABLE_NAME AND c.COLUMN_NAME = kcu.COLUMN_NAME
|
||||||
|
LEFT JOIN
|
||||||
|
INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ON kcu.CONSTRAINT_NAME = pk.CONSTRAINT_NAME AND pk.CONSTRAINT_TYPE = 'PRIMARY KEY'
|
||||||
|
WHERE
|
||||||
|
c.TABLE_NAME = '${tableName}'
|
||||||
|
ORDER BY
|
||||||
|
c.ORDINAL_POSITION
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get the number of affected rows based on query type
|
||||||
|
*/
|
||||||
|
private getAffectedRows(query: string, lastID: number): number {
|
||||||
|
const queryType = query.trim().split(' ')[0].toUpperCase();
|
||||||
|
if (queryType === 'INSERT' && lastID > 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return 0; // For SELECT, unknown for UPDATE/DELETE without additional query
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
import { dbAll } from '../db/index.js';
|
import { dbAll, getListTablesQuery, getDescribeTableQuery, getDatabaseMetadata } from '../db/index.js';
|
||||||
import { getDatabasePath } from '../db/index.js';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle listing resources request
|
* Handle listing resources request
|
||||||
@@ -7,13 +6,24 @@ import { getDatabasePath } from '../db/index.js';
|
|||||||
*/
|
*/
|
||||||
export async function handleListResources() {
|
export async function handleListResources() {
|
||||||
try {
|
try {
|
||||||
const databasePath = getDatabasePath();
|
const dbInfo = getDatabaseMetadata();
|
||||||
const resourceBaseUrl = new URL(`sqlite:///${databasePath}`);
|
const dbType = dbInfo.type;
|
||||||
|
let resourceBaseUrl: URL;
|
||||||
|
|
||||||
|
// Create appropriate URL based on database type
|
||||||
|
if (dbType === 'sqlite' && dbInfo.path) {
|
||||||
|
resourceBaseUrl = new URL(`sqlite:///${dbInfo.path}`);
|
||||||
|
} else if (dbType === 'sqlserver' && dbInfo.server && dbInfo.database) {
|
||||||
|
resourceBaseUrl = new URL(`sqlserver://${dbInfo.server}/${dbInfo.database}`);
|
||||||
|
} else {
|
||||||
|
resourceBaseUrl = new URL(`db:///database`);
|
||||||
|
}
|
||||||
|
|
||||||
const SCHEMA_PATH = "schema";
|
const SCHEMA_PATH = "schema";
|
||||||
|
|
||||||
const result = await dbAll(
|
// Use adapter-specific query to list tables
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
const query = getListTablesQuery();
|
||||||
);
|
const result = await dbAll(query);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
resources: result.map((row: any) => ({
|
resources: result.map((row: any) => ({
|
||||||
@@ -45,8 +55,9 @@ export async function handleReadResource(uri: string) {
|
|||||||
throw new Error("Invalid resource URI");
|
throw new Error("Invalid resource URI");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Query to get column information for a table
|
// Use adapter-specific query to describe the table
|
||||||
const result = await dbAll(`PRAGMA table_info("${tableName}")`);
|
const query = getDescribeTableQuery(tableName!);
|
||||||
|
const result = await dbAll(query);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
contents: [
|
contents: [
|
||||||
|
|||||||
75
src/index.ts
75
src/index.ts
@@ -10,7 +10,7 @@ import {
|
|||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
|
|
||||||
// Import database utils
|
// Import database utils
|
||||||
import { initDatabase, closeDatabase } from './db/index.js';
|
import { initDatabase, closeDatabase, getDatabaseMetadata } from './db/index.js';
|
||||||
|
|
||||||
// Import handlers
|
// Import handlers
|
||||||
import { handleListResources, handleReadResource } from './handlers/resourceHandlers.js';
|
import { handleListResources, handleReadResource } from './handlers/resourceHandlers.js';
|
||||||
@@ -33,11 +33,52 @@ const server = new Server(
|
|||||||
// Parse command line arguments
|
// Parse command line arguments
|
||||||
const args = process.argv.slice(2);
|
const args = process.argv.slice(2);
|
||||||
if (args.length === 0) {
|
if (args.length === 0) {
|
||||||
console.error("Please provide a database file path as a command-line argument");
|
console.error("Please provide database connection information");
|
||||||
|
console.error("Usage for SQLite: node index.js <database_file_path>");
|
||||||
|
console.error("Usage for SQL Server: node index.js --sqlserver --server <server> --database <database> [--user <user> --password <password>]");
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const databasePath = args[0];
|
// Parse arguments to determine database type and connection info
|
||||||
|
let dbType = 'sqlite';
|
||||||
|
let connectionInfo: any = null;
|
||||||
|
|
||||||
|
// Check if using SQL Server
|
||||||
|
if (args.includes('--sqlserver')) {
|
||||||
|
dbType = 'sqlserver';
|
||||||
|
connectionInfo = {
|
||||||
|
server: '',
|
||||||
|
database: '',
|
||||||
|
user: undefined,
|
||||||
|
password: undefined
|
||||||
|
};
|
||||||
|
|
||||||
|
// Parse SQL Server connection parameters
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === '--server' && i + 1 < args.length) {
|
||||||
|
connectionInfo.server = args[i + 1];
|
||||||
|
} else if (args[i] === '--database' && i + 1 < args.length) {
|
||||||
|
connectionInfo.database = args[i + 1];
|
||||||
|
} else if (args[i] === '--user' && i + 1 < args.length) {
|
||||||
|
connectionInfo.user = args[i + 1];
|
||||||
|
} else if (args[i] === '--password' && i + 1 < args.length) {
|
||||||
|
connectionInfo.password = args[i + 1];
|
||||||
|
} else if (args[i] === '--port' && i + 1 < args.length) {
|
||||||
|
connectionInfo.port = parseInt(args[i + 1], 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate SQL Server connection info
|
||||||
|
if (!connectionInfo.server || !connectionInfo.database) {
|
||||||
|
console.error("Error: SQL Server requires --server and --database parameters");
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// SQLite mode (default)
|
||||||
|
dbType = 'sqlite';
|
||||||
|
connectionInfo = args[0]; // First argument is the SQLite file path
|
||||||
|
console.log(`Using SQLite database at path: ${connectionInfo}`);
|
||||||
|
}
|
||||||
|
|
||||||
// Set up request handlers
|
// Set up request handlers
|
||||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||||
@@ -69,13 +110,32 @@ process.on('SIGTERM', async () => {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add global error handler
|
||||||
|
process.on('uncaughtException', (error) => {
|
||||||
|
console.error('Uncaught exception:', error);
|
||||||
|
});
|
||||||
|
|
||||||
|
process.on('unhandledRejection', (reason, promise) => {
|
||||||
|
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start the server
|
* Start the server
|
||||||
*/
|
*/
|
||||||
async function runServer() {
|
async function runServer() {
|
||||||
try {
|
try {
|
||||||
console.log(`Initializing database: ${databasePath}`);
|
console.log(`Initializing ${dbType} database...`);
|
||||||
await initDatabase(databasePath);
|
if (dbType === 'sqlite') {
|
||||||
|
console.log(`Database path: ${connectionInfo}`);
|
||||||
|
} else if (dbType === 'sqlserver') {
|
||||||
|
console.log(`Server: ${connectionInfo.server}, Database: ${connectionInfo.database}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize the database
|
||||||
|
await initDatabase(connectionInfo, dbType);
|
||||||
|
|
||||||
|
const dbInfo = getDatabaseMetadata();
|
||||||
|
console.log(`Connected to ${dbInfo.name} database`);
|
||||||
|
|
||||||
console.log('Starting MCP server...');
|
console.log('Starting MCP server...');
|
||||||
const transport = new StdioServerTransport();
|
const transport = new StdioServerTransport();
|
||||||
@@ -89,4 +149,7 @@ async function runServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start the server
|
// Start the server
|
||||||
runServer().catch(console.error);
|
runServer().catch(error => {
|
||||||
|
console.error("Server initialization failed:", error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { dbAll, dbExec } from '../db/index.js';
|
import { dbAll, dbExec, getListTablesQuery, getDescribeTableQuery } from '../db/index.js';
|
||||||
import { formatSuccessResponse } from '../utils/formatUtils.js';
|
import { formatSuccessResponse } from '../utils/formatUtils.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -56,13 +56,12 @@ export async function dropTable(tableName: string, confirm: boolean) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if table exists
|
// First check if table exists by directly querying for tables
|
||||||
const tableExists = await dbAll(
|
const query = getListTablesQuery();
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
|
const tables = await dbAll(query);
|
||||||
[tableName]
|
const tableNames = tables.map(t => t.name);
|
||||||
);
|
|
||||||
|
|
||||||
if (tableExists.length === 0) {
|
if (!tableNames.includes(tableName)) {
|
||||||
throw new Error(`Table '${tableName}' does not exist`);
|
throw new Error(`Table '${tableName}' does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,9 +83,9 @@ export async function dropTable(tableName: string, confirm: boolean) {
|
|||||||
*/
|
*/
|
||||||
export async function listTables() {
|
export async function listTables() {
|
||||||
try {
|
try {
|
||||||
const tables = await dbAll(
|
// Use adapter-specific query for listing tables
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'"
|
const query = getListTablesQuery();
|
||||||
);
|
const tables = await dbAll(query);
|
||||||
return formatSuccessResponse(tables.map((t) => t.name));
|
return formatSuccessResponse(tables.map((t) => t.name));
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
throw new Error(`Error listing tables: ${error.message}`);
|
throw new Error(`Error listing tables: ${error.message}`);
|
||||||
@@ -104,17 +103,19 @@ export async function describeTable(tableName: string) {
|
|||||||
throw new Error("Table name is required");
|
throw new Error("Table name is required");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if table exists
|
// First check if table exists by directly querying for tables
|
||||||
const tableExists = await dbAll(
|
const query = getListTablesQuery();
|
||||||
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?",
|
const tables = await dbAll(query);
|
||||||
[tableName]
|
const tableNames = tables.map(t => t.name);
|
||||||
);
|
|
||||||
|
|
||||||
if (tableExists.length === 0) {
|
if (!tableNames.includes(tableName)) {
|
||||||
throw new Error(`Table '${tableName}' does not exist`);
|
throw new Error(`Table '${tableName}' does not exist`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const columns = await dbAll(`PRAGMA table_info("${tableName}")`);
|
// Use adapter-specific query for describing tables
|
||||||
|
const descQuery = getDescribeTableQuery(tableName);
|
||||||
|
const columns = await dbAll(descQuery);
|
||||||
|
|
||||||
return formatSuccessResponse(columns.map((col) => ({
|
return formatSuccessResponse(columns.map((col) => ({
|
||||||
name: col.name,
|
name: col.name,
|
||||||
type: col.type,
|
type: col.type,
|
||||||
|
|||||||
Reference in New Issue
Block a user