@williamp29/project-mcp-server 2.0.0 → 3.0.0

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 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", // Path to your OpenAPI spec
59
- authContext: new GlobalAuthContext(new MyCustomAuth())
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 MySQL (driver included).
186
+ - Extensible architecture: Supports **Custom Drivers**!
187
+ - Includes `MySQLDriver` for direct MySQL/MariaDB usage.
@@ -1,7 +1,5 @@
1
1
  import axios from "axios";
2
- import dotenv from "dotenv";
3
2
  import { createAuthContextFromEnv, runRequestHooks } from "./auth/index.js";
4
- dotenv.config();
5
3
  /**
6
4
  * Handles the actual HTTP communication with the API.
7
5
  * Uses axios interceptors to dynamically inject authentication headers and run request hooks.
@@ -2,8 +2,6 @@ import { isIdentifiable } from "./types.js";
2
2
  import { BearerStrategy } from "./strategies/bearer-strategy.js";
3
3
  import { IdentityStrategy } from "./strategies/identity-strategy.js";
4
4
  import { NoAuthStrategy } from "./strategies/no-auth-strategy.js";
5
- import dotenv from "dotenv";
6
- dotenv.config();
7
5
  /**
8
6
  * Standard implementation of AuthContext that wraps an AuthStrategy.
9
7
  * Handles runtime identifier updates for identity-based strategies.
@@ -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 "./index.js";
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, DbConfig } from "./types.js";
1
+ import { DbDriver, DbDriverPermissions } from "./types.js";
2
2
  export declare class DbExecutor {
3
3
  private driver;
4
- private config;
5
- constructor(driver: DbDriver, config: DbConfig);
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
- config;
5
- constructor(driver, config) {
4
+ permissions;
5
+ constructor(driver, permissions = {}) {
6
6
  this.driver = driver;
7
- this.config = config;
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.config.enableRunQuery === false) {
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.config.enableRunUpdateStatement === false) {
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.config.enableRunDeleteStatement !== true) {
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.config.enableRunStatement !== true) {
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 { z } from "zod";
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(executor: DbExecutor);
6
- getToolDefinitions(): {
7
- db_list_tables: {
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 { z } from "zod";
1
+ import { DbExecutor } from "./db-executor.js";
2
2
  export class DbToolGenerator {
3
+ driver;
4
+ permissions;
3
5
  executor;
4
- constructor(executor) {
5
- this.executor = executor;
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
- switch (name) {
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, DbConfig, TableInfo, ColumnInfo, Relationship, TableStats, QueryResult, UpdateResult, DeleteResult, StatementResult } from "../types.js";
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: DbConfig);
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
- const [rows] = await this.getPool().execute(`SELECT TABLE_NAME as name, TABLE_SCHEMA as \`schema\`, TABLE_TYPE as type, TABLE_COMMENT as comment
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 = ?`, [this.config.database]);
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
@@ -1,2 +1,3 @@
1
1
  export { MCPServer } from "./mcp-server.js";
2
+ export type { ApiConfig, MCPServerOptions } from "./mcp-server.js";
2
3
  export * from "./db-explorer/index.js";
@@ -1,5 +1,6 @@
1
- import { AuthContext } from "./api-explorer/auth/index.js";
2
- import type { DbConfig } from "./db-explorer/index.js";
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
- authContext?: AuthContext;
15
- dbConfig?: DbConfig;
15
+ api?: ApiConfig;
16
+ database?: DatabaseConfig;
16
17
  }
17
18
  /**
18
19
  * The main MCP Server implementation that coordinates the OpenAPI parser,
@@ -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
- this.apiExecutor = new ApiExecutor(undefined, this.authContext);
37
- if (options.dbConfig) {
38
- const driver = new MySQLDriver(options.dbConfig);
39
- this.dbExecutor = new DbExecutor(driver, options.dbConfig);
40
- this.dbToolGenerator = new DbToolGenerator(this.dbExecutor);
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",
@@ -108,7 +108,7 @@ export class MCPServer {
108
108
  });
109
109
  });
110
110
  }
111
- this.server.registerTool("set_identity", {
111
+ this.server.registerTool("api_set_identity", {
112
112
  description: "Set the identity (identifier) for future API requests. Requires an 'identity' auth strategy to be active.",
113
113
  inputSchema: z.object({
114
114
  identifier: z.string().describe("The new identity value (e.g., user UID)."),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamp29/project-mcp-server",
3
- "version": "2.0.0",
3
+ "version": "3.0.0",
4
4
  "description": "A ModelContextProtocol server to let agents discover your project, such as APIs (using OpenAPI) or other resources.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",