@williamp29/project-mcp-server 2.0.1 → 3.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -7
- package/dist/api-explorer/index.d.ts +16 -0
- package/dist/cli.js +2 -19
- package/dist/db-explorer/db-executor.d.ts +5 -4
- package/dist/db-explorer/db-executor.js +23 -9
- package/dist/db-explorer/db-tool-generator.d.ts +6 -64
- package/dist/db-explorer/db-tool-generator.js +9 -87
- package/dist/db-explorer/drivers/mysql-driver.d.ts +14 -3
- package/dist/db-explorer/drivers/mysql-driver.js +130 -3
- package/dist/db-explorer/index.d.ts +6 -1
- package/dist/db-explorer/index.js +21 -0
- package/dist/db-explorer/types.d.ts +19 -1
- package/dist/index.d.ts +1 -0
- package/dist/mcp-server.d.ts +5 -4
- package/dist/mcp-server.js +8 -8
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -42,26 +42,92 @@ npm install @williamp29/project-mcp-server
|
|
|
42
42
|
Create a file (e.g., `mcp-server.ts`) to configure and start your server:
|
|
43
43
|
|
|
44
44
|
```typescript
|
|
45
|
-
import { MCPServer } from "@williamp29/project-mcp-server";
|
|
45
|
+
import { MCPServer, MySQLDriver } from "@williamp29/project-mcp-server";
|
|
46
46
|
import { AuthStrategy, GlobalAuthContext } from "@williamp29/project-mcp-server/api-explorer";
|
|
47
47
|
|
|
48
|
-
// (Optional) Define a custom authentication strategy
|
|
48
|
+
// 1. (Optional) Define a custom authentication strategy for APIs
|
|
49
49
|
class MyCustomAuth implements AuthStrategy {
|
|
50
50
|
name = "MyCustomAuth";
|
|
51
51
|
async getHeaders() {
|
|
52
|
-
// Fetch token from vault, env, or database
|
|
53
52
|
return { "Authorization": `Bearer ${process.env.MY_API_TOKEN}` };
|
|
54
53
|
}
|
|
55
54
|
}
|
|
56
55
|
|
|
56
|
+
// 2. (Optional) Initialize a database driver
|
|
57
|
+
// You can use the built-in MySQLDriver
|
|
58
|
+
const mysqlDriver = new MySQLDriver({
|
|
59
|
+
host: process.env.DB_HOST || "localhost",
|
|
60
|
+
port: 3306,
|
|
61
|
+
user: "root",
|
|
62
|
+
password: "password",
|
|
63
|
+
database: "my_database"
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// 3. Create and start the server
|
|
57
67
|
const server = new MCPServer({
|
|
58
|
-
specPath: "./openapi-spec.json",
|
|
59
|
-
|
|
68
|
+
specPath: "./openapi-spec.json",
|
|
69
|
+
api: {
|
|
70
|
+
baseUrl: "https://api.example.com",
|
|
71
|
+
authContext: new GlobalAuthContext(new MyCustomAuth()),
|
|
72
|
+
},
|
|
73
|
+
database: {
|
|
74
|
+
driver: mysqlDriver,
|
|
75
|
+
permissions: {
|
|
76
|
+
enableRunQuery: true, // Default: true
|
|
77
|
+
enableRunUpdateStatement: true, // Default: true
|
|
78
|
+
enableRunDeleteStatement: false, // Default: false
|
|
79
|
+
enableRunStatement: false // Default: false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
60
82
|
});
|
|
61
83
|
|
|
62
84
|
server.start().catch(console.error);
|
|
63
85
|
```
|
|
64
86
|
|
|
87
|
+
### 3. Custom Database Drivers
|
|
88
|
+
You can implement your own database driver by satisfying the `DbDriver` interface. This allows you to use any database (PostgreSQL, SQLite, etc.) with the MCP server.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { MCPServer, DbDriver, DbDriverPermissions, DbDriverToolDefinitions } from "@williamp29/project-mcp-server";
|
|
92
|
+
|
|
93
|
+
class MyPostgresDriver implements DbDriver {
|
|
94
|
+
name = "postgres";
|
|
95
|
+
|
|
96
|
+
async connect() { /* ... */ }
|
|
97
|
+
async disconnect() { /* ... */ }
|
|
98
|
+
|
|
99
|
+
// Implement introspection methods
|
|
100
|
+
async listTables() { /* ... */ }
|
|
101
|
+
async listTableNames() { return ["table1", "table2"]; }
|
|
102
|
+
async describeTable(table: string) { /* ... */ }
|
|
103
|
+
// ... other DbDriver methods
|
|
104
|
+
|
|
105
|
+
// Define which tools this driver supports
|
|
106
|
+
getToolDefinitions(permissions: DbDriverPermissions): DbDriverToolDefinitions {
|
|
107
|
+
return {
|
|
108
|
+
db_list_tables: { description: "List all tables" },
|
|
109
|
+
// ...
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// Handle tool execution
|
|
114
|
+
async handleToolCall(name: string, args: any) {
|
|
115
|
+
if (name === "db_list_tables") return this.listTables();
|
|
116
|
+
// ...
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const server = new MCPServer({
|
|
121
|
+
specPath: "./openapi-spec.json",
|
|
122
|
+
api: {
|
|
123
|
+
baseUrl: "https://api.example.com",
|
|
124
|
+
},
|
|
125
|
+
database: {
|
|
126
|
+
driver: new MyPostgresDriver()
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
```
|
|
130
|
+
|
|
65
131
|
### 3. Add Script to package.json
|
|
66
132
|
```json
|
|
67
133
|
{
|
|
@@ -113,8 +179,9 @@ These variables control the server behavior. They are automatically loaded if yo
|
|
|
113
179
|
- `api_set_identity`: Switch the active user context for API calls dynamically during a session.
|
|
114
180
|
|
|
115
181
|
- **Database Explorer**:
|
|
116
|
-
- Inspect schemas with `db_list_tables`, `db_describe_tables`, and `db_get_schemas`.
|
|
182
|
+
- Inspect schemas with `db_list_tables`, `db_list_table_names`, `db_describe_tables`, and `db_get_schemas`.
|
|
117
183
|
- Discover database structure with `db_get_relationships` (supports table filtering).
|
|
118
184
|
- Analyze data with `db_get_table_stats` and `db_sample_rows`.
|
|
119
185
|
- Run validated SQL with `db_run_query`, `db_run_update_statement`, and `db_run_delete_statement`.
|
|
120
|
-
- Supports
|
|
186
|
+
- Extensible architecture: Supports **Custom Drivers**!
|
|
187
|
+
- Includes `MySQLDriver` for direct MySQL/MariaDB usage.
|
|
@@ -2,3 +2,19 @@ export { OpenAPIParser } from "./openapi-parser.js";
|
|
|
2
2
|
export { ToolGenerator } from "./tool-generator.js";
|
|
3
3
|
export { ApiExecutor } from "./api-executor.js";
|
|
4
4
|
export * from "./auth/index.js";
|
|
5
|
+
import { AuthContext } from "./auth/index.js";
|
|
6
|
+
/**
|
|
7
|
+
* Configuration for the API Explorer.
|
|
8
|
+
*/
|
|
9
|
+
export interface ApiConfig {
|
|
10
|
+
/**
|
|
11
|
+
* Optional custom authentication context.
|
|
12
|
+
* If not provided, it will be created from environment variables.
|
|
13
|
+
*/
|
|
14
|
+
authContext?: AuthContext;
|
|
15
|
+
/**
|
|
16
|
+
* The target API base URL.
|
|
17
|
+
* If not provided, it will default to env.PROJECT_MCP_API_BASE_URL or "http://localhost:5000".
|
|
18
|
+
*/
|
|
19
|
+
baseUrl?: string;
|
|
20
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "dotenv/config";
|
|
3
|
-
import { MCPServer } from "./
|
|
3
|
+
import { MCPServer } from "./mcp-server.js";
|
|
4
4
|
import path from "path";
|
|
5
5
|
const specPath = process.env.PROJECT_MCP_OPENAPI_SPEC || path.resolve(process.cwd(), "openapi-spec.json");
|
|
6
|
-
let dbConfig = undefined;
|
|
7
|
-
if (process.env.PROJECT_MCP_DB_HOST) {
|
|
8
|
-
dbConfig = {
|
|
9
|
-
driver: process.env.PROJECT_MCP_DB_DRIVER || "mysql",
|
|
10
|
-
host: process.env.PROJECT_MCP_DB_HOST || "localhost",
|
|
11
|
-
port: parseInt(process.env.PROJECT_MCP_DB_PORT || "3306", 10),
|
|
12
|
-
user: process.env.PROJECT_MCP_DB_USER || "",
|
|
13
|
-
password: process.env.PROJECT_MCP_DB_PASSWORD || "",
|
|
14
|
-
database: process.env.PROJECT_MCP_DB_DATABASE || "",
|
|
15
|
-
poolSize: parseInt(process.env.PROJECT_MCP_DB_POOL_SIZE || "10", 10),
|
|
16
|
-
enableRunQuery: process.env.PROJECT_MCP_DB_ENABLE_QUERY !== "false",
|
|
17
|
-
enableRunUpdateStatement: process.env.PROJECT_MCP_DB_ENABLE_UPDATE !== "false",
|
|
18
|
-
enableRunDeleteStatement: process.env.PROJECT_MCP_DB_ENABLE_DELETE === "true",
|
|
19
|
-
enableRunStatement: process.env.PROJECT_MCP_DB_ENABLE_STATEMENT === "true",
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
6
|
const server = new MCPServer({
|
|
23
|
-
specPath
|
|
24
|
-
dbConfig
|
|
7
|
+
specPath
|
|
25
8
|
});
|
|
26
9
|
server.start().catch((error) => {
|
|
27
10
|
console.error("Failed to start MCP server:", error);
|
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import { DbDriver,
|
|
1
|
+
import { DbDriver, DbDriverPermissions } from "./types.js";
|
|
2
2
|
export declare class DbExecutor {
|
|
3
3
|
private driver;
|
|
4
|
-
private
|
|
5
|
-
constructor(driver: DbDriver,
|
|
4
|
+
private permissions;
|
|
5
|
+
constructor(driver: DbDriver, permissions?: DbDriverPermissions);
|
|
6
6
|
getDriver(): DbDriver;
|
|
7
7
|
connect(): Promise<void>;
|
|
8
8
|
disconnect(): Promise<void>;
|
|
9
|
-
listTables(): Promise<import("./types.js").TableInfo[]>;
|
|
9
|
+
listTables(tables?: string[]): Promise<import("./types.js").TableInfo[]>;
|
|
10
10
|
describeTable(table: string): Promise<import("./types.js").ColumnInfo[]>;
|
|
11
11
|
describeTables(tables: string[]): Promise<Record<string, any>>;
|
|
12
12
|
getTableSchema(table: string): Promise<string>;
|
|
@@ -18,4 +18,5 @@ export declare class DbExecutor {
|
|
|
18
18
|
runUpdateStatement(query: string): Promise<import("./types.js").UpdateResult>;
|
|
19
19
|
runDeleteStatement(query: string): Promise<import("./types.js").DeleteResult>;
|
|
20
20
|
runStatement(query: string): Promise<import("./types.js").StatementResult>;
|
|
21
|
+
handleToolCall(name: string, args: any): Promise<any>;
|
|
21
22
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { SqlValidator } from "./sql-validator.js";
|
|
2
2
|
export class DbExecutor {
|
|
3
3
|
driver;
|
|
4
|
-
|
|
5
|
-
constructor(driver,
|
|
4
|
+
permissions;
|
|
5
|
+
constructor(driver, permissions = {}) {
|
|
6
6
|
this.driver = driver;
|
|
7
|
-
this.
|
|
7
|
+
this.permissions = permissions;
|
|
8
8
|
}
|
|
9
9
|
getDriver() {
|
|
10
10
|
return this.driver;
|
|
@@ -15,8 +15,8 @@ export class DbExecutor {
|
|
|
15
15
|
async disconnect() {
|
|
16
16
|
await this.driver.disconnect();
|
|
17
17
|
}
|
|
18
|
-
async listTables() {
|
|
19
|
-
return await this.driver.listTables();
|
|
18
|
+
async listTables(tables) {
|
|
19
|
+
return await this.driver.listTables(tables);
|
|
20
20
|
}
|
|
21
21
|
async describeTable(table) {
|
|
22
22
|
return await this.driver.describeTable(table);
|
|
@@ -48,30 +48,44 @@ export class DbExecutor {
|
|
|
48
48
|
return await this.driver.sampleRows(table, limit);
|
|
49
49
|
}
|
|
50
50
|
async runQuery(query) {
|
|
51
|
-
if (this.
|
|
51
|
+
if (this.permissions.enableRunQuery === false) {
|
|
52
52
|
throw new Error("Tool 'run_query' is disabled.");
|
|
53
53
|
}
|
|
54
54
|
SqlValidator.isSelectOnly(query);
|
|
55
55
|
return await this.driver.executeQuery(query);
|
|
56
56
|
}
|
|
57
57
|
async runUpdateStatement(query) {
|
|
58
|
-
if (this.
|
|
58
|
+
if (this.permissions.enableRunUpdateStatement === false) {
|
|
59
59
|
throw new Error("Tool 'run_update_statement' is disabled.");
|
|
60
60
|
}
|
|
61
61
|
SqlValidator.isUpdateOnly(query);
|
|
62
62
|
return await this.driver.executeUpdate(query);
|
|
63
63
|
}
|
|
64
64
|
async runDeleteStatement(query) {
|
|
65
|
-
if (this.
|
|
65
|
+
if (this.permissions.enableRunDeleteStatement !== true) {
|
|
66
66
|
throw new Error("Tool 'run_delete_statement' is disabled.");
|
|
67
67
|
}
|
|
68
68
|
SqlValidator.isDeleteOnly(query);
|
|
69
69
|
return await this.driver.executeDelete(query);
|
|
70
70
|
}
|
|
71
71
|
async runStatement(query) {
|
|
72
|
-
if (this.
|
|
72
|
+
if (this.permissions.enableRunStatement !== true) {
|
|
73
73
|
throw new Error("Tool 'run_statement' is disabled.");
|
|
74
74
|
}
|
|
75
75
|
return await this.driver.executeStatement(query);
|
|
76
76
|
}
|
|
77
|
+
async handleToolCall(name, args) {
|
|
78
|
+
switch (name) {
|
|
79
|
+
case "db_run_query":
|
|
80
|
+
return await this.runQuery(args.query);
|
|
81
|
+
case "db_run_update_statement":
|
|
82
|
+
return await this.runUpdateStatement(args.query);
|
|
83
|
+
case "db_run_delete_statement":
|
|
84
|
+
return await this.runDeleteStatement(args.query);
|
|
85
|
+
case "db_run_statement":
|
|
86
|
+
return await this.runStatement(args.query);
|
|
87
|
+
default:
|
|
88
|
+
return await this.driver.handleToolCall(name, args);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
77
91
|
}
|
|
@@ -1,67 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { DbExecutor } from "./db-executor.js";
|
|
1
|
+
import { DbDriver, DbDriverPermissions } from "./types.js";
|
|
3
2
|
export declare class DbToolGenerator {
|
|
3
|
+
private driver;
|
|
4
|
+
private permissions;
|
|
4
5
|
private executor;
|
|
5
|
-
constructor(
|
|
6
|
-
getToolDefinitions():
|
|
7
|
-
|
|
8
|
-
description: string;
|
|
9
|
-
};
|
|
10
|
-
db_describe_tables: {
|
|
11
|
-
description: string;
|
|
12
|
-
inputSchema: z.ZodObject<{
|
|
13
|
-
tables: z.ZodArray<z.ZodString>;
|
|
14
|
-
}, z.core.$strip>;
|
|
15
|
-
};
|
|
16
|
-
db_get_schemas: {
|
|
17
|
-
description: string;
|
|
18
|
-
inputSchema: z.ZodObject<{
|
|
19
|
-
tables: z.ZodArray<z.ZodString>;
|
|
20
|
-
}, z.core.$strip>;
|
|
21
|
-
};
|
|
22
|
-
db_get_relationships: {
|
|
23
|
-
description: string;
|
|
24
|
-
inputSchema: z.ZodObject<{
|
|
25
|
-
tables: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
26
|
-
}, z.core.$strip>;
|
|
27
|
-
};
|
|
28
|
-
db_get_table_stats: {
|
|
29
|
-
description: string;
|
|
30
|
-
inputSchema: z.ZodObject<{
|
|
31
|
-
table: z.ZodString;
|
|
32
|
-
}, z.core.$strip>;
|
|
33
|
-
};
|
|
34
|
-
db_sample_rows: {
|
|
35
|
-
description: string;
|
|
36
|
-
inputSchema: z.ZodObject<{
|
|
37
|
-
table: z.ZodString;
|
|
38
|
-
limit: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
39
|
-
}, z.core.$strip>;
|
|
40
|
-
};
|
|
41
|
-
db_run_query: {
|
|
42
|
-
description: string;
|
|
43
|
-
inputSchema: z.ZodObject<{
|
|
44
|
-
query: z.ZodString;
|
|
45
|
-
}, z.core.$strip>;
|
|
46
|
-
};
|
|
47
|
-
db_run_update_statement: {
|
|
48
|
-
description: string;
|
|
49
|
-
inputSchema: z.ZodObject<{
|
|
50
|
-
query: z.ZodString;
|
|
51
|
-
}, z.core.$strip>;
|
|
52
|
-
};
|
|
53
|
-
db_run_delete_statement: {
|
|
54
|
-
description: string;
|
|
55
|
-
inputSchema: z.ZodObject<{
|
|
56
|
-
query: z.ZodString;
|
|
57
|
-
}, z.core.$strip>;
|
|
58
|
-
};
|
|
59
|
-
db_run_statement: {
|
|
60
|
-
description: string;
|
|
61
|
-
inputSchema: z.ZodObject<{
|
|
62
|
-
query: z.ZodString;
|
|
63
|
-
}, z.core.$strip>;
|
|
64
|
-
};
|
|
65
|
-
};
|
|
66
|
-
handleToolCall(name: string, args: any): Promise<any[] | Record<string, any> | import("./types.js").TableStats | import("./types.js").QueryResult | import("./types.js").DeleteResult | import("./types.js").StatementResult>;
|
|
6
|
+
constructor(driver: DbDriver, permissions?: DbDriverPermissions);
|
|
7
|
+
getToolDefinitions(): import("./types.js").DbDriverToolDefinitions;
|
|
8
|
+
handleToolCall(name: string, args: any): Promise<any>;
|
|
67
9
|
}
|
|
@@ -1,95 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { DbExecutor } from "./db-executor.js";
|
|
2
2
|
export class DbToolGenerator {
|
|
3
|
+
driver;
|
|
4
|
+
permissions;
|
|
3
5
|
executor;
|
|
4
|
-
constructor(
|
|
5
|
-
this.
|
|
6
|
+
constructor(driver, permissions = {}) {
|
|
7
|
+
this.driver = driver;
|
|
8
|
+
this.permissions = permissions;
|
|
9
|
+
this.executor = new DbExecutor(driver, permissions);
|
|
6
10
|
}
|
|
7
11
|
getToolDefinitions() {
|
|
8
|
-
return
|
|
9
|
-
db_list_tables: {
|
|
10
|
-
description: "List all tables in the current database.",
|
|
11
|
-
},
|
|
12
|
-
db_describe_tables: {
|
|
13
|
-
description: "Get detailed information about columns for multiple tables.",
|
|
14
|
-
inputSchema: z.object({
|
|
15
|
-
tables: z.array(z.string()).describe("List of table names to describe."),
|
|
16
|
-
}),
|
|
17
|
-
},
|
|
18
|
-
db_get_schemas: {
|
|
19
|
-
description: "Get the DDL statements for multiple tables.",
|
|
20
|
-
inputSchema: z.object({
|
|
21
|
-
tables: z.array(z.string()).describe("List of table names."),
|
|
22
|
-
}),
|
|
23
|
-
},
|
|
24
|
-
db_get_relationships: {
|
|
25
|
-
description: "Get foreign key relationships in the database, optionally filtered by tables.",
|
|
26
|
-
inputSchema: z.object({
|
|
27
|
-
tables: z.array(z.string()).optional().describe("Optional list of table names to filter relationships by."),
|
|
28
|
-
}),
|
|
29
|
-
},
|
|
30
|
-
db_get_table_stats: {
|
|
31
|
-
description: "Get row count and other statistics for a table.",
|
|
32
|
-
inputSchema: z.object({
|
|
33
|
-
table: z.string().describe("The name of the table."),
|
|
34
|
-
}),
|
|
35
|
-
},
|
|
36
|
-
db_sample_rows: {
|
|
37
|
-
description: "Fetch sample rows from a table.",
|
|
38
|
-
inputSchema: z.object({
|
|
39
|
-
table: z.string().describe("The name of the table."),
|
|
40
|
-
limit: z.number().optional().default(10).describe("Maximum number of rows to fetch."),
|
|
41
|
-
}),
|
|
42
|
-
},
|
|
43
|
-
db_run_query: {
|
|
44
|
-
description: "Execute a read-only SELECT query. Blocks modification keywords.",
|
|
45
|
-
inputSchema: z.object({
|
|
46
|
-
query: z.string().describe("The SQL SELECT query to execute."),
|
|
47
|
-
}),
|
|
48
|
-
},
|
|
49
|
-
db_run_update_statement: {
|
|
50
|
-
description: "Execute an INSERT or UPDATE statement. Blocks DELETE and other dangerous keywords.",
|
|
51
|
-
inputSchema: z.object({
|
|
52
|
-
query: z.string().describe("The SQL INSERT/UPDATE statement."),
|
|
53
|
-
}),
|
|
54
|
-
},
|
|
55
|
-
db_run_delete_statement: {
|
|
56
|
-
description: "Execute a DELETE or TRUNCATE statement. Blocks DROP/ALTER.",
|
|
57
|
-
inputSchema: z.object({
|
|
58
|
-
query: z.string().describe("The SQL DELETE/TRUNCATE statement."),
|
|
59
|
-
}),
|
|
60
|
-
},
|
|
61
|
-
db_run_statement: {
|
|
62
|
-
description: "Execute any SQL statement. Full access, use with extreme caution. Disabled by default.",
|
|
63
|
-
inputSchema: z.object({
|
|
64
|
-
query: z.string().describe("The SQL statement to execute."),
|
|
65
|
-
}),
|
|
66
|
-
},
|
|
67
|
-
};
|
|
12
|
+
return this.driver.getToolDefinitions(this.permissions);
|
|
68
13
|
}
|
|
69
14
|
async handleToolCall(name, args) {
|
|
70
|
-
|
|
71
|
-
case "db_list_tables":
|
|
72
|
-
return await this.executor.listTables();
|
|
73
|
-
case "db_describe_tables":
|
|
74
|
-
return await this.executor.describeTables(args.tables);
|
|
75
|
-
case "db_get_schemas":
|
|
76
|
-
return await this.executor.getTableSchemas(args.tables);
|
|
77
|
-
case "db_get_relationships":
|
|
78
|
-
return await this.executor.getRelationships(args.tables);
|
|
79
|
-
case "db_get_table_stats":
|
|
80
|
-
return await this.executor.getTableStats(args.table);
|
|
81
|
-
case "db_sample_rows":
|
|
82
|
-
return await this.executor.sampleRows(args.table, args.limit);
|
|
83
|
-
case "db_run_query":
|
|
84
|
-
return await this.executor.runQuery(args.query);
|
|
85
|
-
case "db_run_update_statement":
|
|
86
|
-
return await this.executor.runUpdateStatement(args.query);
|
|
87
|
-
case "db_run_delete_statement":
|
|
88
|
-
return await this.executor.runDeleteStatement(args.query);
|
|
89
|
-
case "db_run_statement":
|
|
90
|
-
return await this.executor.runStatement(args.query);
|
|
91
|
-
default:
|
|
92
|
-
throw new Error(`Unknown database tool: ${name}`);
|
|
93
|
-
}
|
|
15
|
+
return await this.executor.handleToolCall(name, args);
|
|
94
16
|
}
|
|
95
17
|
}
|
|
@@ -1,13 +1,22 @@
|
|
|
1
|
-
import { DbDriver,
|
|
1
|
+
import { DbDriver, TableInfo, ColumnInfo, Relationship, TableStats, QueryResult, UpdateResult, DeleteResult, StatementResult, DbDriverPermissions, DbDriverToolDefinitions } from "../types.js";
|
|
2
|
+
export interface MySQLDriverConfig {
|
|
3
|
+
host: string;
|
|
4
|
+
port: number;
|
|
5
|
+
user: string;
|
|
6
|
+
password: string;
|
|
7
|
+
database: string;
|
|
8
|
+
poolSize?: number;
|
|
9
|
+
}
|
|
2
10
|
export declare class MySQLDriver implements DbDriver {
|
|
3
11
|
name: string;
|
|
4
12
|
private pool;
|
|
5
13
|
private config;
|
|
6
|
-
constructor(config:
|
|
14
|
+
constructor(config: MySQLDriverConfig);
|
|
7
15
|
connect(): Promise<void>;
|
|
8
16
|
disconnect(): Promise<void>;
|
|
9
17
|
private getPool;
|
|
10
|
-
listTables(): Promise<TableInfo[]>;
|
|
18
|
+
listTables(tables?: string[]): Promise<TableInfo[]>;
|
|
19
|
+
listTableNames(): Promise<string[]>;
|
|
11
20
|
describeTable(table: string): Promise<ColumnInfo[]>;
|
|
12
21
|
getTableSchema(table: string): Promise<string>;
|
|
13
22
|
getRelationships(tables?: string[]): Promise<Relationship[]>;
|
|
@@ -17,4 +26,6 @@ export declare class MySQLDriver implements DbDriver {
|
|
|
17
26
|
executeUpdate(query: string): Promise<UpdateResult>;
|
|
18
27
|
executeDelete(query: string): Promise<DeleteResult>;
|
|
19
28
|
executeStatement(query: string): Promise<StatementResult>;
|
|
29
|
+
getToolDefinitions(permissions?: DbDriverPermissions): DbDriverToolDefinitions;
|
|
30
|
+
handleToolCall(name: string, args: any): Promise<any>;
|
|
20
31
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import mysql from "mysql2/promise";
|
|
2
|
+
import { z } from "zod";
|
|
2
3
|
export class MySQLDriver {
|
|
3
4
|
name = "mysql";
|
|
4
5
|
pool = null;
|
|
@@ -34,10 +35,17 @@ export class MySQLDriver {
|
|
|
34
35
|
throw new Error("Driver not connected. Call connect() first.");
|
|
35
36
|
return this.pool;
|
|
36
37
|
}
|
|
37
|
-
async listTables() {
|
|
38
|
-
|
|
38
|
+
async listTables(tables) {
|
|
39
|
+
let query = `SELECT TABLE_NAME as name, TABLE_SCHEMA as \`schema\`, TABLE_TYPE as type, TABLE_COMMENT as comment
|
|
39
40
|
FROM INFORMATION_SCHEMA.TABLES
|
|
40
|
-
WHERE TABLE_SCHEMA =
|
|
41
|
+
WHERE TABLE_SCHEMA = ?`;
|
|
42
|
+
const params = [this.config.database];
|
|
43
|
+
if (tables && tables.length > 0) {
|
|
44
|
+
const placeholders = tables.map(() => "?").join(",");
|
|
45
|
+
query += ` AND TABLE_NAME IN (${placeholders})`;
|
|
46
|
+
params.push(...tables);
|
|
47
|
+
}
|
|
48
|
+
const [rows] = await this.getPool().execute(query, params);
|
|
41
49
|
return rows.map((r) => ({
|
|
42
50
|
name: r.name,
|
|
43
51
|
schema: r.schema,
|
|
@@ -45,6 +53,12 @@ export class MySQLDriver {
|
|
|
45
53
|
comment: r.comment || undefined
|
|
46
54
|
}));
|
|
47
55
|
}
|
|
56
|
+
async listTableNames() {
|
|
57
|
+
const [rows] = await this.getPool().execute(`SELECT TABLE_NAME as name
|
|
58
|
+
FROM INFORMATION_SCHEMA.TABLES
|
|
59
|
+
WHERE TABLE_SCHEMA = ?`, [this.config.database]);
|
|
60
|
+
return rows.map((r) => r.name);
|
|
61
|
+
}
|
|
48
62
|
async describeTable(table) {
|
|
49
63
|
const [rows] = await this.getPool().execute(`SELECT
|
|
50
64
|
COLUMN_NAME as name,
|
|
@@ -167,4 +181,117 @@ export class MySQLDriver {
|
|
|
167
181
|
data: result
|
|
168
182
|
};
|
|
169
183
|
}
|
|
184
|
+
getToolDefinitions(permissions = {}) {
|
|
185
|
+
const tools = {
|
|
186
|
+
db_list_tables: {
|
|
187
|
+
description: "List all tables in the current database.",
|
|
188
|
+
inputSchema: z.object({
|
|
189
|
+
tables: z.array(z.string()).optional().describe("Optional list of table names to filter by. If not provided, all tables are returned."),
|
|
190
|
+
}),
|
|
191
|
+
},
|
|
192
|
+
db_list_table_names: {
|
|
193
|
+
description: "List all table names in the current database.",
|
|
194
|
+
},
|
|
195
|
+
db_describe_tables: {
|
|
196
|
+
description: "Get detailed information about columns for multiple tables.",
|
|
197
|
+
inputSchema: z.object({
|
|
198
|
+
tables: z.array(z.string()).describe("List of table names to describe."),
|
|
199
|
+
}),
|
|
200
|
+
},
|
|
201
|
+
db_get_schemas: {
|
|
202
|
+
description: "Get the DDL statements for multiple tables.",
|
|
203
|
+
inputSchema: z.object({
|
|
204
|
+
tables: z.array(z.string()).describe("List of table names."),
|
|
205
|
+
}),
|
|
206
|
+
},
|
|
207
|
+
db_get_relationships: {
|
|
208
|
+
description: "Get foreign key relationships in the database, optionally filtered by tables.",
|
|
209
|
+
inputSchema: z.object({
|
|
210
|
+
tables: z.array(z.string()).optional().describe("Optional list of table names to filter relationships by."),
|
|
211
|
+
}),
|
|
212
|
+
},
|
|
213
|
+
db_get_table_stats: {
|
|
214
|
+
description: "Get row count and other statistics for a table.",
|
|
215
|
+
inputSchema: z.object({
|
|
216
|
+
table: z.string().describe("The name of the table."),
|
|
217
|
+
}),
|
|
218
|
+
},
|
|
219
|
+
db_sample_rows: {
|
|
220
|
+
description: "Fetch sample rows from a table.",
|
|
221
|
+
inputSchema: z.object({
|
|
222
|
+
table: z.string().describe("The name of the table."),
|
|
223
|
+
limit: z.number().optional().default(10).describe("Maximum number of rows to fetch."),
|
|
224
|
+
}),
|
|
225
|
+
},
|
|
226
|
+
};
|
|
227
|
+
if (permissions.enableRunQuery !== false) {
|
|
228
|
+
tools.db_run_query = {
|
|
229
|
+
description: "Execute a read-only SELECT query. Blocks modification keywords.",
|
|
230
|
+
inputSchema: z.object({
|
|
231
|
+
query: z.string().describe("The SQL SELECT query to execute."),
|
|
232
|
+
}),
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (permissions.enableRunUpdateStatement !== false) {
|
|
236
|
+
tools.db_run_update_statement = {
|
|
237
|
+
description: "Execute an INSERT or UPDATE statement. Blocks DELETE and other dangerous keywords.",
|
|
238
|
+
inputSchema: z.object({
|
|
239
|
+
query: z.string().describe("The SQL INSERT/UPDATE statement."),
|
|
240
|
+
}),
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
if (permissions.enableRunDeleteStatement === true) {
|
|
244
|
+
tools.db_run_delete_statement = {
|
|
245
|
+
description: "Execute a DELETE or TRUNCATE statement. Blocks DROP/ALTER.",
|
|
246
|
+
inputSchema: z.object({
|
|
247
|
+
query: z.string().describe("The SQL DELETE/TRUNCATE statement."),
|
|
248
|
+
}),
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
if (permissions.enableRunStatement === true) {
|
|
252
|
+
tools.db_run_statement = {
|
|
253
|
+
description: "Execute any SQL statement. Full access, use with extreme caution. Disabled by default.",
|
|
254
|
+
inputSchema: z.object({
|
|
255
|
+
query: z.string().describe("The SQL statement to execute."),
|
|
256
|
+
}),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
return tools;
|
|
260
|
+
}
|
|
261
|
+
async handleToolCall(name, args) {
|
|
262
|
+
switch (name) {
|
|
263
|
+
case "db_list_tables":
|
|
264
|
+
return await this.listTables(args.tables);
|
|
265
|
+
case "db_list_table_names":
|
|
266
|
+
return await this.listTableNames();
|
|
267
|
+
case "db_describe_tables":
|
|
268
|
+
const describeResults = {};
|
|
269
|
+
for (const table of args.tables) {
|
|
270
|
+
describeResults[table] = await this.describeTable(table);
|
|
271
|
+
}
|
|
272
|
+
return describeResults;
|
|
273
|
+
case "db_get_schemas":
|
|
274
|
+
const schemaResults = {};
|
|
275
|
+
for (const table of args.tables) {
|
|
276
|
+
schemaResults[table] = await this.getTableSchema(table);
|
|
277
|
+
}
|
|
278
|
+
return schemaResults;
|
|
279
|
+
case "db_get_relationships":
|
|
280
|
+
return await this.getRelationships(args.tables);
|
|
281
|
+
case "db_get_table_stats":
|
|
282
|
+
return await this.getTableStats(args.table);
|
|
283
|
+
case "db_sample_rows":
|
|
284
|
+
return await this.sampleRows(args.table, args.limit);
|
|
285
|
+
case "db_run_query":
|
|
286
|
+
return await this.executeQuery(args.query);
|
|
287
|
+
case "db_run_update_statement":
|
|
288
|
+
return await this.executeUpdate(args.query);
|
|
289
|
+
case "db_run_delete_statement":
|
|
290
|
+
return await this.executeDelete(args.query);
|
|
291
|
+
case "db_run_statement":
|
|
292
|
+
return await this.executeStatement(args.query);
|
|
293
|
+
default:
|
|
294
|
+
throw new Error(`Unknown database tool: ${name}`);
|
|
295
|
+
}
|
|
296
|
+
}
|
|
170
297
|
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
|
+
import { DbDriver, DbDriverPermissions } from "./types.js";
|
|
1
2
|
export { DbExecutor } from "./db-executor.js";
|
|
2
3
|
export { DbToolGenerator } from "./db-tool-generator.js";
|
|
3
|
-
export type { DbConfig } from "./types.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
export * from "./drivers/index.js";
|
|
6
|
+
export interface DatabaseConfig {
|
|
7
|
+
driver: DbDriver;
|
|
8
|
+
permissions?: DbDriverPermissions;
|
|
9
|
+
}
|
|
10
|
+
export declare function createDbConfigFromEnv(): DatabaseConfig | undefined;
|
|
@@ -1,4 +1,25 @@
|
|
|
1
|
+
import { MySQLDriver } from "./drivers/mysql-driver.js";
|
|
1
2
|
export { DbExecutor } from "./db-executor.js";
|
|
2
3
|
export { DbToolGenerator } from "./db-tool-generator.js";
|
|
3
4
|
export * from "./types.js";
|
|
4
5
|
export * from "./drivers/index.js";
|
|
6
|
+
export function createDbConfigFromEnv() {
|
|
7
|
+
if (!process.env.PROJECT_MCP_DB_DATABASE && !process.env.PROJECT_MCP_DB_HOST) {
|
|
8
|
+
return undefined;
|
|
9
|
+
}
|
|
10
|
+
const driver = new MySQLDriver({
|
|
11
|
+
host: process.env.PROJECT_MCP_DB_HOST || "localhost",
|
|
12
|
+
port: parseInt(process.env.PROJECT_MCP_DB_PORT || "3306", 10),
|
|
13
|
+
user: process.env.PROJECT_MCP_DB_USER || "",
|
|
14
|
+
password: process.env.PROJECT_MCP_DB_PASSWORD || "",
|
|
15
|
+
database: process.env.PROJECT_MCP_DB_DATABASE || "",
|
|
16
|
+
poolSize: parseInt(process.env.PROJECT_MCP_DB_POOL_SIZE || "10", 10),
|
|
17
|
+
});
|
|
18
|
+
const permissions = {
|
|
19
|
+
enableRunQuery: process.env.PROJECT_MCP_DB_ENABLE_QUERY !== "false",
|
|
20
|
+
enableRunUpdateStatement: process.env.PROJECT_MCP_DB_ENABLE_UPDATE !== "false",
|
|
21
|
+
enableRunDeleteStatement: process.env.PROJECT_MCP_DB_ENABLE_DELETE === "true",
|
|
22
|
+
enableRunStatement: process.env.PROJECT_MCP_DB_ENABLE_STATEMENT === "true",
|
|
23
|
+
};
|
|
24
|
+
return { driver, permissions };
|
|
25
|
+
}
|
|
@@ -1,8 +1,21 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
export interface DbDriverToolDefinition {
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema?: z.ZodType;
|
|
5
|
+
}
|
|
6
|
+
export type DbDriverToolDefinitions = Record<string, DbDriverToolDefinition>;
|
|
7
|
+
export interface DbDriverPermissions {
|
|
8
|
+
enableRunQuery?: boolean;
|
|
9
|
+
enableRunUpdateStatement?: boolean;
|
|
10
|
+
enableRunDeleteStatement?: boolean;
|
|
11
|
+
enableRunStatement?: boolean;
|
|
12
|
+
}
|
|
1
13
|
export interface DbDriver {
|
|
2
14
|
name: string;
|
|
3
15
|
connect(): Promise<void>;
|
|
4
16
|
disconnect(): Promise<void>;
|
|
5
|
-
listTables(): Promise<TableInfo[]>;
|
|
17
|
+
listTables(tables?: string[]): Promise<TableInfo[]>;
|
|
18
|
+
listTableNames(): Promise<string[]>;
|
|
6
19
|
describeTable(table: string): Promise<ColumnInfo[]>;
|
|
7
20
|
getTableSchema(table: string): Promise<string>;
|
|
8
21
|
getRelationships(tables?: string[]): Promise<Relationship[]>;
|
|
@@ -12,7 +25,12 @@ export interface DbDriver {
|
|
|
12
25
|
executeUpdate(query: string): Promise<UpdateResult>;
|
|
13
26
|
executeDelete(query: string): Promise<DeleteResult>;
|
|
14
27
|
executeStatement(query: string): Promise<StatementResult>;
|
|
28
|
+
getToolDefinitions(permissions?: DbDriverPermissions): DbDriverToolDefinitions;
|
|
29
|
+
handleToolCall(name: string, args: any): Promise<any>;
|
|
15
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated Use driver-specific configuration and DbDriverPermissions instead.
|
|
33
|
+
*/
|
|
16
34
|
export interface DbConfig {
|
|
17
35
|
driver: 'mysql';
|
|
18
36
|
host: string;
|
package/dist/index.d.ts
CHANGED
package/dist/mcp-server.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
1
|
+
import { ApiConfig } from "./api-explorer/index.js";
|
|
2
|
+
import type { DatabaseConfig } from "./db-explorer/index.js";
|
|
3
|
+
export type { ApiConfig };
|
|
3
4
|
/**
|
|
4
5
|
* The main MCP Server implementation that coordinates the OpenAPI parser,
|
|
5
6
|
* tool generation, and API execution.
|
|
@@ -11,8 +12,8 @@ import type { DbConfig } from "./db-explorer/index.js";
|
|
|
11
12
|
*/
|
|
12
13
|
export interface MCPServerOptions {
|
|
13
14
|
specPath: string;
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
api?: ApiConfig;
|
|
16
|
+
database?: DatabaseConfig;
|
|
16
17
|
}
|
|
17
18
|
/**
|
|
18
19
|
* The main MCP Server implementation that coordinates the OpenAPI parser,
|
package/dist/mcp-server.js
CHANGED
|
@@ -5,8 +5,7 @@ import { OpenAPIParser } from "./api-explorer/openapi-parser.js";
|
|
|
5
5
|
import { ToolGenerator } from "./api-explorer/tool-generator.js";
|
|
6
6
|
import { ApiExecutor } from "./api-explorer/api-executor.js";
|
|
7
7
|
import { createAuthContextFromEnv } from "./api-explorer/auth/index.js";
|
|
8
|
-
import { DbExecutor, DbToolGenerator } from "./db-explorer/index.js";
|
|
9
|
-
import { MySQLDriver } from "./db-explorer/drivers/mysql-driver.js";
|
|
8
|
+
import { createDbConfigFromEnv, DbExecutor, DbToolGenerator } from "./db-explorer/index.js";
|
|
10
9
|
/**
|
|
11
10
|
* The main MCP Server implementation that coordinates the OpenAPI parser,
|
|
12
11
|
* tool generation, and API execution.
|
|
@@ -32,12 +31,13 @@ export class MCPServer {
|
|
|
32
31
|
this.specPath = options.specPath;
|
|
33
32
|
this.parser = new OpenAPIParser();
|
|
34
33
|
this.toolGenerator = new ToolGenerator(this.parser);
|
|
35
|
-
this.authContext = options.authContext || createAuthContextFromEnv();
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
this.
|
|
34
|
+
this.authContext = options.api?.authContext || createAuthContextFromEnv();
|
|
35
|
+
const apiBaseUrl = options.api?.baseUrl;
|
|
36
|
+
const dbConfig = options.database || createDbConfigFromEnv();
|
|
37
|
+
this.apiExecutor = new ApiExecutor(apiBaseUrl, this.authContext);
|
|
38
|
+
if (dbConfig) {
|
|
39
|
+
this.dbExecutor = new DbExecutor(dbConfig.driver, dbConfig.permissions);
|
|
40
|
+
this.dbToolGenerator = new DbToolGenerator(dbConfig.driver, dbConfig.permissions);
|
|
41
41
|
}
|
|
42
42
|
this.server = new McpServer({
|
|
43
43
|
name: "project-mcp-server",
|
package/package.json
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@williamp29/project-mcp-server",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.1",
|
|
4
4
|
"description": "A ModelContextProtocol server to let agents discover your project, such as APIs (using OpenAPI) or other resources.",
|
|
5
|
+
"repository": "https://github.com/WilliamPinto-Olmos/project-mcp-server",
|
|
5
6
|
"main": "dist/index.js",
|
|
6
7
|
"types": "dist/index.d.ts",
|
|
7
8
|
"bin": {
|