@williamp29/project-mcp-server 1.1.0 → 2.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
@@ -1,153 +1,120 @@
1
- # Your project's MCP Server
1
+ # Project MCP Server
2
2
 
3
- A powerful Model Context Protocol (MCP) server that dynamically serves context about your project. It can explore and interact with any API defined by an OpenAPI specification.
3
+ A powerful **Model Context Protocol (MCP)** server that dynamically serves context about your project. It acts as a bridge, allowing LLM agents to explore and interact with your project's APIs (via OpenAPI) and databases.
4
4
 
5
- ## For LLM Agents: How to use this MCP
5
+ ## Quick Start (Zero Config)
6
6
 
7
- This server provides a set of "meta-tools" that allow you to discover and interact with an API without needing individual tools for every endpoint.
7
+ The easiest way to use this server is to run it directly with `npx`. This requires no code changes to your project.
8
8
 
9
- ### Recommended Workflow:
10
- 1. **Discovery**: Start by calling `get_tags` to understand the broad areas of the API.
11
- 2. **Listing**: Use `get_tag_endpoints` or `get_all_endpoints` to see available actions for a specific topic.
12
- 3. **Details**: Call `get_endpoint` for a specific path and method to see exactly what parameters and request body are required.
13
- 4. **Execution**: Use `call_endpoint` to perform the actual API request.
9
+ ### Run with npx
14
10
 
15
- ### Identity & Impersonation:
16
- If the server is configured with an `identity` strategy, you can use `set_identity` to act on behalf of a specific user (e.g., passing a UID).
11
+ **For Cursor / Claude Desktop:**
12
+ Add this to your MCP settings configuration:
17
13
 
18
- ---
19
-
20
- ## Installation & Setup
21
-
22
- ### 1. Build the project
23
- ```bash
24
- npm install
25
- npm run build
26
- ```
27
-
28
- ### 2. Configure Environment Variables
29
- Create a `.env` file (see `.env.example`):
30
- - `PROJECT_MCP_API_BASE_URL`: The target API URL.
31
- - `PROJECT_MCP_AUTH_TYPE`: `bearer`, `identity`, or `none`.
32
- - `PROJECT_MCP_AUTH_IDENTIFIABLE`: (Optional) e.g., `UID:`.
33
- - `PROJECT_MCP_AUTH_IDENTIFIER`: Default ID value.
34
-
35
- ### 3. Usage in Cursor / Claude Desktop
36
- Add a new MCP server with the following command:
37
- ```bash
38
- node /path/to/dist/cli.js
14
+ ```json
15
+ {
16
+ "mcpServers": {
17
+ "my-project": {
18
+ "command": "npx",
19
+ "args": ["-y", "@williamp29/project-mcp-server"],
20
+ "env": {
21
+ "PROJECT_MCP_API_BASE_URL": "...",
22
+ "PROJECT_MCP_DB_HOST": "..."
23
+ }
24
+ }
25
+ }
26
+ }
39
27
  ```
28
+ *(Note: You can pass environment variables directly in the JSON config or load them from a file if your MCP client supports it.)*
40
29
 
41
- Or if installed via npm:
42
- ```bash
43
- npx @williamp29/project-mcp-server
44
- ```
30
+ ---
45
31
 
46
- ## Integration in Your Project
32
+ ## Library Integration (Advanced)
47
33
 
48
- The recommended way to use this package is to install it in your project and create a custom entry point.
34
+ For deep integration, install the package as a dependency in your Node.js project. This allows you to customize authentication, add custom tools, or use it programmatically.
49
35
 
50
- ### Step 1: Install
36
+ ### 1. Install
51
37
  ```bash
52
38
  npm install @williamp29/project-mcp-server
53
39
  ```
54
40
 
55
- ### Step 2: Create your MCP entry file
56
- Create a file called `mcp-serve.js` (or `.ts`) in your project root:
41
+ ### 2. Create an Entry Point
42
+ Create a file (e.g., `mcp-server.ts`) to configure and start your server:
57
43
 
58
- ```javascript
59
- // mcp-serve.js
44
+ ```typescript
60
45
  import { MCPServer } from "@williamp29/project-mcp-server";
61
46
  import { AuthStrategy, GlobalAuthContext } from "@williamp29/project-mcp-server/api-explorer";
62
47
 
63
- // Your custom authentication logic
64
- class MyAuth implements AuthStrategy {
65
- name = "MyAuth";
48
+ // (Optional) Define a custom authentication strategy
49
+ class MyCustomAuth implements AuthStrategy {
50
+ name = "MyCustomAuth";
66
51
  async getHeaders() {
67
- // Read from your own config, database, vault, etc.
52
+ // Fetch token from vault, env, or database
68
53
  return { "Authorization": `Bearer ${process.env.MY_API_TOKEN}` };
69
54
  }
70
55
  }
71
56
 
72
- const authContext = new GlobalAuthContext(new MyAuth());
73
- const server = new MCPServer("./openapi-spec.json", authContext);
74
- server.start();
57
+ const server = new MCPServer({
58
+ specPath: "./openapi-spec.json", // Path to your OpenAPI spec
59
+ authContext: new GlobalAuthContext(new MyCustomAuth())
60
+ });
61
+
62
+ server.start().catch(console.error);
75
63
  ```
76
64
 
77
- ### Step 3: Add the npm script
78
- In your project's `package.json`:
65
+ ### 3. Add Script to package.json
79
66
  ```json
80
67
  {
81
68
  "scripts": {
82
- "mcp:serve": "node mcp-serve.js"
69
+ "mcp": "ts-node mcp-server.ts"
83
70
  }
84
71
  }
85
72
  ```
86
73
 
87
- ### Step 4: Configure Cursor
88
- In your Cursor MCP settings (`mcp_settings.json`):
74
+ ### 4. Use in Cursor
89
75
  ```json
90
76
  {
91
- "my-api-mcp": {
92
- "command": "npm",
93
- "args": ["run", "mcp:serve"],
94
- "cwd": "/absolute/path/to/your/project"
77
+ "mcpServers": {
78
+ "my-integrated-project": {
79
+ "command": "npm",
80
+ "args": ["run", "mcp"],
81
+ "cwd": "/absolute/path/to/your/project"
82
+ }
95
83
  }
96
84
  }
97
85
  ```
98
86
 
99
- Now Cursor will run your custom script whenever it needs to use the MCP server.
100
-
101
87
  ---
102
88
 
103
- ## Programmatic Usage
89
+ ## Configuration Reference
104
90
 
105
- If you are using this as a library in your own Node.js project:
91
+ ### Environment Variables
92
+ These variables control the server behavior. They are automatically loaded if you use the `npx` method or if you use `dotenv` in your custom script.
106
93
 
107
- ### Basic Setup
108
- ```typescript
109
- import { MCPServer } from "@williamp29/project-mcp-server";
94
+ | Variable | Description | Default |
95
+ | :--- | :--- | :--- |
96
+ | `PROJECT_MCP_API_BASE_URL` | Base URL of the target API | - |
97
+ | `PROJECT_MCP_AUTH_TYPE` | Auth method: `bearer`, `identity`, `none` | `none` |
98
+ | `PROJECT_MCP_AUTH_IDENTIFIER` | Default ID for `identity` auth | - |
99
+ | `PROJECT_MCP_DB_HOST` | Database hostname | - |
100
+ | `PROJECT_MCP_DB_PORT` | Database port | `3306` |
101
+ | `PROJECT_MCP_DB_USER` | Database username | - |
102
+ | `PROJECT_MCP_DB_PASSWORD` | Database password | - |
103
+ | `PROJECT_MCP_DB_DATABASE` | Database name | - |
110
104
 
111
- const server = new MCPServer("./openapi-spec.json");
112
- server.start().catch(console.error);
113
- ```
114
-
115
- ### Custom Authentication Strategy
116
- You can implement your own logic for fetching or rotating tokens:
117
-
118
- ```typescript
119
- import { MCPServer } from "@williamp29/project-mcp-server";
120
- import { AuthStrategy, GlobalAuthContext } from "@williamp29/project-mcp-server/api-explorer";
121
-
122
- class MyCustomAuth implements AuthStrategy {
123
- name = "MyCustomAuth";
124
- async getHeaders() {
125
- const token = await fetchTokenFromVault();
126
- return { "X-Custom-Auth": token };
127
- }
128
- }
129
-
130
- const authContext = new GlobalAuthContext(new MyCustomAuth());
131
- const server = new MCPServer("./spec.json", authContext);
132
- server.start();
133
- ```
134
-
135
- ### Request Hooks
136
- Add custom logic to every request (logging, tracing, extra headers):
137
-
138
- ```typescript
139
- import { addRequestHook } from "@williamp29/project-mcp-server/api-explorer";
140
-
141
- addRequestHook((config) => {
142
- console.error(`[API] ${config.method?.toUpperCase()} ${config.url}`);
143
- config.headers["X-Request-ID"] = "mcp-123";
144
- return config;
145
- });
146
- ```
105
+ ---
147
106
 
148
107
  ## Features
149
- - **Dynamic Exploration**: Automatically parses Swagger/OpenAPI 3.0 specs.
150
- - **Smart Execution**: Handles path parameters (e.g., `{id}`), query strings, and JSON bodies.
151
- - **Flexible Auth**: Support for standard Bearer tokens and custom Identity headers.
152
- - **Interceptors**: Easily extendable with request hooks for logging or custom headers.
153
- - **Impersonation**: Built-in `set_identity` tool to switch users on the fly.
108
+
109
+ - **API Explorer**:
110
+ - Automatically parses OpenAPI 3.0 specifications.
111
+ - Exposes tools like `api_get_tags`, `api_get_endpoints`, and `api_call_endpoint`.
112
+ - Supports dynamic path parameters and JSON bodies.
113
+ - `api_set_identity`: Switch the active user context for API calls dynamically during a session.
114
+
115
+ - **Database Explorer**:
116
+ - Inspect schemas with `db_list_tables`, `db_describe_tables`, and `db_get_schemas`.
117
+ - Discover database structure with `db_get_relationships` (supports table filtering).
118
+ - Analyze data with `db_get_table_stats` and `db_sample_rows`.
119
+ - Run validated SQL with `db_run_query`, `db_run_update_statement`, and `db_run_delete_statement`.
120
+ - Supports MySQL (driver included).
@@ -24,14 +24,12 @@ export class ApiExecutor {
24
24
  });
25
25
  // Add interceptor for dynamic auth and hooks
26
26
  this.client.interceptors.request.use(async (config) => {
27
- // 1. Inject Dynamic Auth Headers
28
27
  const authHeaders = await this.authContext.getHeaders();
29
28
  // Log for debugging (stderr)
30
29
  if (Object.keys(authHeaders).length > 0) {
31
30
  console.error(`[ApiExecutor] Injecting auth headers from strategy: ${this.authContext.strategy.name}`);
32
31
  }
33
32
  Object.assign(config.headers, authHeaders);
34
- // 2. Run Request Hooks
35
33
  return await runRequestHooks(config);
36
34
  });
37
35
  }
@@ -20,32 +20,32 @@ export declare class ToolGenerator {
20
20
  * These definitions are used by MCPServer to register tools with the SDK.
21
21
  */
22
22
  getToolDefinitions(): {
23
- get_tags: {
23
+ api_get_tags: {
24
24
  description: string;
25
25
  };
26
- get_tag_endpoints: {
26
+ api_get_tag_endpoints: {
27
27
  description: string;
28
28
  inputSchema: z.ZodObject<{
29
29
  tag: z.ZodString;
30
30
  }, z.core.$strip>;
31
31
  };
32
- get_tags_endpoints: {
32
+ api_get_tags_endpoints: {
33
33
  description: string;
34
34
  inputSchema: z.ZodObject<{
35
35
  tags: z.ZodArray<z.ZodString>;
36
36
  }, z.core.$strip>;
37
37
  };
38
- get_all_endpoints: {
38
+ api_get_all_endpoints: {
39
39
  description: string;
40
40
  };
41
- get_endpoint: {
41
+ api_get_endpoint: {
42
42
  description: string;
43
43
  inputSchema: z.ZodObject<{
44
44
  method: z.ZodString;
45
45
  path: z.ZodString;
46
46
  }, z.core.$strip>;
47
47
  };
48
- get_endpoints: {
48
+ api_get_endpoints: {
49
49
  description: string;
50
50
  inputSchema: z.ZodObject<{
51
51
  requests: z.ZodArray<z.ZodObject<{
@@ -54,7 +54,7 @@ export declare class ToolGenerator {
54
54
  }, z.core.$strip>>;
55
55
  }, z.core.$strip>;
56
56
  };
57
- call_endpoint: {
57
+ api_call_endpoint: {
58
58
  description: string;
59
59
  inputSchema: z.ZodObject<{
60
60
  method: z.ZodString;
@@ -17,32 +17,32 @@ export class ToolGenerator {
17
17
  */
18
18
  getToolDefinitions() {
19
19
  return {
20
- get_tags: {
20
+ api_get_tags: {
21
21
  description: "Get all unique tags defined in the API spec. This helps to group and discover endpoints.",
22
22
  },
23
- get_tag_endpoints: {
23
+ api_get_tag_endpoints: {
24
24
  description: "Get all endpoints associated with a specific tag. Returns a summary of each endpoint.",
25
25
  inputSchema: z.object({
26
26
  tag: z.string().describe("The tag to filter endpoints by."),
27
27
  }),
28
28
  },
29
- get_tags_endpoints: {
29
+ api_get_tags_endpoints: {
30
30
  description: "Get all endpoints associated with multiple tags. Returns a summary of each endpoint.",
31
31
  inputSchema: z.object({
32
32
  tags: z.array(z.string()).describe("The tags to filter endpoints by."),
33
33
  }),
34
34
  },
35
- get_all_endpoints: {
35
+ api_get_all_endpoints: {
36
36
  description: "Get a summarized list of all endpoints available in the API.",
37
37
  },
38
- get_endpoint: {
38
+ api_get_endpoint: {
39
39
  description: "Get detailed information about a specific endpoint, including parameters and request body schema.",
40
40
  inputSchema: z.object({
41
41
  method: z.string().describe("The HTTP method (GET, POST, etc.)."),
42
42
  path: z.string().describe("The endpoint path."),
43
43
  }),
44
44
  },
45
- get_endpoints: {
45
+ api_get_endpoints: {
46
46
  description: "Get detailed information for multiple specific endpoints.",
47
47
  inputSchema: z.object({
48
48
  requests: z.array(z.object({
@@ -51,7 +51,7 @@ export class ToolGenerator {
51
51
  })).describe("List of endpoint requests."),
52
52
  }),
53
53
  },
54
- call_endpoint: {
54
+ api_call_endpoint: {
55
55
  description: "Execute a request to a project's endpoint using the specified parameters and body.",
56
56
  inputSchema: z.object({
57
57
  method: z.string().describe("The HTTP method."),
@@ -64,17 +64,17 @@ export class ToolGenerator {
64
64
  }
65
65
  handleToolCall(name, args) {
66
66
  switch (name) {
67
- case "get_tags":
67
+ case "api_get_tags":
68
68
  return this.parser.getTags();
69
- case "get_tag_endpoints":
69
+ case "api_get_tag_endpoints":
70
70
  return this.parser.getEndpointsByTag(args.tag);
71
- case "get_tags_endpoints":
71
+ case "api_get_tags_endpoints":
72
72
  return this.parser.getEndpointsByTags(args.tags);
73
- case "get_all_endpoints":
73
+ case "api_get_all_endpoints":
74
74
  return this.parser.getEndpoints();
75
- case "get_endpoint":
75
+ case "api_get_endpoint":
76
76
  return this.parser.getEndpoint(args.method, args.path);
77
- case "get_endpoints":
77
+ case "api_get_endpoints":
78
78
  return args.requests.map((r) => this.parser.getEndpoint(r.method, r.path));
79
79
  default:
80
80
  throw new Error(`Unknown tool: ${name}`);
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ import "dotenv/config";
package/dist/cli.js CHANGED
@@ -1,12 +1,29 @@
1
1
  #!/usr/bin/env node
2
+ import "dotenv/config";
2
3
  import { MCPServer } from "./index.js";
3
4
  import path from "path";
4
- import { fileURLToPath } from "url";
5
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
6
- const DEFAULT_SPEC_PATH = path.resolve(__dirname, "../openapi-spec.json");
7
- const specPath = process.argv[2] || DEFAULT_SPEC_PATH;
8
- const server = new MCPServer(specPath);
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
+ const server = new MCPServer({
23
+ specPath,
24
+ dbConfig
25
+ });
9
26
  server.start().catch((error) => {
10
- console.error("Fatal error in MCP server:", error);
27
+ console.error("Failed to start MCP server:", error);
11
28
  process.exit(1);
12
29
  });
@@ -0,0 +1,21 @@
1
+ import { DbDriver, DbConfig } from "./types.js";
2
+ export declare class DbExecutor {
3
+ private driver;
4
+ private config;
5
+ constructor(driver: DbDriver, config: DbConfig);
6
+ getDriver(): DbDriver;
7
+ connect(): Promise<void>;
8
+ disconnect(): Promise<void>;
9
+ listTables(): Promise<import("./types.js").TableInfo[]>;
10
+ describeTable(table: string): Promise<import("./types.js").ColumnInfo[]>;
11
+ describeTables(tables: string[]): Promise<Record<string, any>>;
12
+ getTableSchema(table: string): Promise<string>;
13
+ getTableSchemas(tables: string[]): Promise<Record<string, string>>;
14
+ getRelationships(tables?: string[]): Promise<import("./types.js").Relationship[]>;
15
+ getTableStats(table: string): Promise<import("./types.js").TableStats>;
16
+ sampleRows(table: string, limit?: number): Promise<any[]>;
17
+ runQuery(query: string): Promise<import("./types.js").QueryResult>;
18
+ runUpdateStatement(query: string): Promise<import("./types.js").UpdateResult>;
19
+ runDeleteStatement(query: string): Promise<import("./types.js").DeleteResult>;
20
+ runStatement(query: string): Promise<import("./types.js").StatementResult>;
21
+ }
@@ -0,0 +1,77 @@
1
+ import { SqlValidator } from "./sql-validator.js";
2
+ export class DbExecutor {
3
+ driver;
4
+ config;
5
+ constructor(driver, config) {
6
+ this.driver = driver;
7
+ this.config = config;
8
+ }
9
+ getDriver() {
10
+ return this.driver;
11
+ }
12
+ async connect() {
13
+ await this.driver.connect();
14
+ }
15
+ async disconnect() {
16
+ await this.driver.disconnect();
17
+ }
18
+ async listTables() {
19
+ return await this.driver.listTables();
20
+ }
21
+ async describeTable(table) {
22
+ return await this.driver.describeTable(table);
23
+ }
24
+ async describeTables(tables) {
25
+ const results = {};
26
+ for (const table of tables) {
27
+ results[table] = await this.driver.describeTable(table);
28
+ }
29
+ return results;
30
+ }
31
+ async getTableSchema(table) {
32
+ return await this.driver.getTableSchema(table);
33
+ }
34
+ async getTableSchemas(tables) {
35
+ const results = {};
36
+ for (const table of tables) {
37
+ results[table] = await this.driver.getTableSchema(table);
38
+ }
39
+ return results;
40
+ }
41
+ async getRelationships(tables) {
42
+ return await this.driver.getRelationships(tables);
43
+ }
44
+ async getTableStats(table) {
45
+ return await this.driver.getTableStats(table);
46
+ }
47
+ async sampleRows(table, limit) {
48
+ return await this.driver.sampleRows(table, limit);
49
+ }
50
+ async runQuery(query) {
51
+ if (this.config.enableRunQuery === false) {
52
+ throw new Error("Tool 'run_query' is disabled.");
53
+ }
54
+ SqlValidator.isSelectOnly(query);
55
+ return await this.driver.executeQuery(query);
56
+ }
57
+ async runUpdateStatement(query) {
58
+ if (this.config.enableRunUpdateStatement === false) {
59
+ throw new Error("Tool 'run_update_statement' is disabled.");
60
+ }
61
+ SqlValidator.isUpdateOnly(query);
62
+ return await this.driver.executeUpdate(query);
63
+ }
64
+ async runDeleteStatement(query) {
65
+ if (this.config.enableRunDeleteStatement !== true) {
66
+ throw new Error("Tool 'run_delete_statement' is disabled.");
67
+ }
68
+ SqlValidator.isDeleteOnly(query);
69
+ return await this.driver.executeDelete(query);
70
+ }
71
+ async runStatement(query) {
72
+ if (this.config.enableRunStatement !== true) {
73
+ throw new Error("Tool 'run_statement' is disabled.");
74
+ }
75
+ return await this.driver.executeStatement(query);
76
+ }
77
+ }
@@ -0,0 +1,67 @@
1
+ import { z } from "zod";
2
+ import { DbExecutor } from "./db-executor.js";
3
+ export declare class DbToolGenerator {
4
+ 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>;
67
+ }
@@ -0,0 +1,95 @@
1
+ import { z } from "zod";
2
+ export class DbToolGenerator {
3
+ executor;
4
+ constructor(executor) {
5
+ this.executor = executor;
6
+ }
7
+ 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
+ };
68
+ }
69
+ 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
+ }
94
+ }
95
+ }
@@ -0,0 +1 @@
1
+ export { MySQLDriver } from "./mysql-driver.js";
@@ -0,0 +1 @@
1
+ export { MySQLDriver } from "./mysql-driver.js";
@@ -0,0 +1,20 @@
1
+ import { DbDriver, DbConfig, TableInfo, ColumnInfo, Relationship, TableStats, QueryResult, UpdateResult, DeleteResult, StatementResult } from "../types.js";
2
+ export declare class MySQLDriver implements DbDriver {
3
+ name: string;
4
+ private pool;
5
+ private config;
6
+ constructor(config: DbConfig);
7
+ connect(): Promise<void>;
8
+ disconnect(): Promise<void>;
9
+ private getPool;
10
+ listTables(): Promise<TableInfo[]>;
11
+ describeTable(table: string): Promise<ColumnInfo[]>;
12
+ getTableSchema(table: string): Promise<string>;
13
+ getRelationships(tables?: string[]): Promise<Relationship[]>;
14
+ getTableStats(table: string): Promise<TableStats>;
15
+ sampleRows(table: string, limit?: number): Promise<any[]>;
16
+ executeQuery(query: string): Promise<QueryResult>;
17
+ executeUpdate(query: string): Promise<UpdateResult>;
18
+ executeDelete(query: string): Promise<DeleteResult>;
19
+ executeStatement(query: string): Promise<StatementResult>;
20
+ }
@@ -0,0 +1,170 @@
1
+ import mysql from "mysql2/promise";
2
+ export class MySQLDriver {
3
+ name = "mysql";
4
+ pool = null;
5
+ config;
6
+ constructor(config) {
7
+ this.config = config;
8
+ }
9
+ async connect() {
10
+ if (this.pool)
11
+ return;
12
+ this.pool = mysql.createPool({
13
+ host: this.config.host,
14
+ port: this.config.port,
15
+ user: this.config.user,
16
+ password: this.config.password,
17
+ database: this.config.database,
18
+ connectionLimit: this.config.poolSize || 10,
19
+ waitForConnections: true,
20
+ queueLimit: 0
21
+ });
22
+ const connection = await this.pool.getConnection();
23
+ connection.release();
24
+ console.error(`[MySQLDriver] Connected to ${this.config.host}:${this.config.port}/${this.config.database}`);
25
+ }
26
+ async disconnect() {
27
+ if (this.pool) {
28
+ await this.pool.end();
29
+ this.pool = null;
30
+ }
31
+ }
32
+ getPool() {
33
+ if (!this.pool)
34
+ throw new Error("Driver not connected. Call connect() first.");
35
+ return this.pool;
36
+ }
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
39
+ FROM INFORMATION_SCHEMA.TABLES
40
+ WHERE TABLE_SCHEMA = ?`, [this.config.database]);
41
+ return rows.map((r) => ({
42
+ name: r.name,
43
+ schema: r.schema,
44
+ type: r.type,
45
+ comment: r.comment || undefined
46
+ }));
47
+ }
48
+ async describeTable(table) {
49
+ const [rows] = await this.getPool().execute(`SELECT
50
+ COLUMN_NAME as name,
51
+ DATA_TYPE as type,
52
+ COLUMN_TYPE as fullType,
53
+ IS_NULLABLE as nullable,
54
+ COLUMN_DEFAULT as defaultValue,
55
+ COLUMN_KEY as columnKey,
56
+ EXTRA as extra,
57
+ COLUMN_COMMENT as comment,
58
+ CHARACTER_MAXIMUM_LENGTH as characterMaxLength,
59
+ NUMERIC_PRECISION as numericPrecision,
60
+ NUMERIC_SCALE as numericScale
61
+ FROM INFORMATION_SCHEMA.COLUMNS
62
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?
63
+ ORDER BY ORDINAL_POSITION`, [this.config.database, table]);
64
+ return rows.map((r) => {
65
+ const isEnum = r.fullType.startsWith("enum(") || r.fullType.startsWith("set(");
66
+ let enumValues;
67
+ if (isEnum) {
68
+ const matches = r.fullType.match(/'([^']*)'/g);
69
+ if (matches) {
70
+ enumValues = matches.map((m) => m.replace(/'/g, ""));
71
+ }
72
+ }
73
+ return {
74
+ name: r.name,
75
+ type: r.type,
76
+ fullType: r.fullType,
77
+ nullable: r.nullable === "YES",
78
+ defaultValue: r.defaultValue,
79
+ isPrimaryKey: r.columnKey === "PRI",
80
+ isForeignKey: r.columnKey === "MUL",
81
+ isAutoIncrement: r.extra?.includes("auto_increment") || false,
82
+ comment: r.comment || undefined,
83
+ extra: r.extra || undefined,
84
+ enumValues,
85
+ characterMaxLength: r.characterMaxLength ? Number(r.characterMaxLength) : undefined,
86
+ numericPrecision: r.numericPrecision ? Number(r.numericPrecision) : undefined,
87
+ numericScale: r.numericScale ? Number(r.numericScale) : undefined
88
+ };
89
+ });
90
+ }
91
+ async getTableSchema(table) {
92
+ const [rows] = await this.getPool().execute(`SHOW CREATE TABLE \`${table}\``);
93
+ return rows[0]?.["Create Table"] || "";
94
+ }
95
+ async getRelationships(tables) {
96
+ let query = `SELECT
97
+ CONSTRAINT_NAME as constraintName,
98
+ TABLE_NAME as fromTable,
99
+ COLUMN_NAME as fromColumn,
100
+ REFERENCED_TABLE_NAME as toTable,
101
+ REFERENCED_COLUMN_NAME as toColumn
102
+ FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
103
+ WHERE TABLE_SCHEMA = ? AND REFERENCED_TABLE_NAME IS NOT NULL`;
104
+ const params = [this.config.database];
105
+ if (tables && tables.length > 0) {
106
+ const placeholders = tables.map(() => "?").join(",");
107
+ query += ` AND (TABLE_NAME IN (${placeholders}) OR REFERENCED_TABLE_NAME IN (${placeholders}))`;
108
+ params.push(...tables, ...tables);
109
+ }
110
+ const [rows] = await this.getPool().execute(query, params);
111
+ return rows.map((r) => ({
112
+ constraintName: r.constraintName,
113
+ fromTable: r.fromTable,
114
+ fromColumn: r.fromColumn,
115
+ toTable: r.toTable,
116
+ toColumn: r.toColumn
117
+ }));
118
+ }
119
+ async getTableStats(table) {
120
+ const [rows] = await this.getPool().execute(`SELECT
121
+ TABLE_ROWS as rowCount,
122
+ DATA_LENGTH as dataLength,
123
+ INDEX_LENGTH as indexLength,
124
+ CREATE_TIME as createTime,
125
+ UPDATE_TIME as updateTime
126
+ FROM INFORMATION_SCHEMA.TABLES
127
+ WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?`, [this.config.database, table]);
128
+ const r = rows[0];
129
+ if (!r)
130
+ throw new Error(`Table ${table} not found.`);
131
+ return {
132
+ rowCount: Number(r.rowCount),
133
+ dataLength: Number(r.dataLength),
134
+ indexLength: Number(r.indexLength),
135
+ createTime: r.createTime ? new Date(r.createTime) : undefined,
136
+ updateTime: r.updateTime ? new Date(r.updateTime) : undefined
137
+ };
138
+ }
139
+ async sampleRows(table, limit = 10) {
140
+ const safeLimit = Math.max(1, Math.floor(Number(limit) || 10));
141
+ const [rows] = await this.getPool().execute(`SELECT * FROM \`${table}\` LIMIT ${safeLimit}`);
142
+ return rows;
143
+ }
144
+ async executeQuery(query) {
145
+ const [rows] = await this.getPool().execute(query);
146
+ return { rows };
147
+ }
148
+ async executeUpdate(query) {
149
+ const [result] = await this.getPool().execute(query);
150
+ return {
151
+ affectedRows: result.affectedRows,
152
+ insertId: result.insertId,
153
+ message: result.info
154
+ };
155
+ }
156
+ async executeDelete(query) {
157
+ const [result] = await this.getPool().execute(query);
158
+ return {
159
+ affectedRows: result.affectedRows,
160
+ message: result.info
161
+ };
162
+ }
163
+ async executeStatement(query) {
164
+ const [result] = await this.getPool().execute(query);
165
+ return {
166
+ success: true,
167
+ data: result
168
+ };
169
+ }
170
+ }
@@ -0,0 +1,5 @@
1
+ export { DbExecutor } from "./db-executor.js";
2
+ export { DbToolGenerator } from "./db-tool-generator.js";
3
+ export type { DbConfig } from "./types.js";
4
+ export * from "./types.js";
5
+ export * from "./drivers/index.js";
@@ -0,0 +1,4 @@
1
+ export { DbExecutor } from "./db-executor.js";
2
+ export { DbToolGenerator } from "./db-tool-generator.js";
3
+ export * from "./types.js";
4
+ export * from "./drivers/index.js";
@@ -0,0 +1,10 @@
1
+ export declare class SqlValidator {
2
+ /**
3
+ * Blocks keywords that are not allowed for a given operation.
4
+ * Checks case-insensitively and avoids being fooled by keywords inside strings/comments.
5
+ */
6
+ static validate(query: string, blockedKeywords: string[]): void;
7
+ static isSelectOnly(query: string): void;
8
+ static isUpdateOnly(query: string): void;
9
+ static isDeleteOnly(query: string): void;
10
+ }
@@ -0,0 +1,44 @@
1
+ export class SqlValidator {
2
+ /**
3
+ * Blocks keywords that are not allowed for a given operation.
4
+ * Checks case-insensitively and avoids being fooled by keywords inside strings/comments.
5
+ */
6
+ static validate(query, blockedKeywords) {
7
+ if (blockedKeywords.length === 0)
8
+ return;
9
+ const queryWithoutComments = query
10
+ .replace(/\/\*[\s\S]*?\*\/|([^:]|^)\/\/.*|--.*/g, "$1")
11
+ .trim();
12
+ const queryWithoutLiterals = queryWithoutComments
13
+ .replace(/'(?:''|[^'])*'/g, "''")
14
+ .replace(/"(?:""|[^"])*"/g, '""')
15
+ .replace(/`(?:``|[^`])*`/g, "``");
16
+ const tokens = queryWithoutLiterals.toUpperCase().split(/\s+/);
17
+ for (const keyword of blockedKeywords) {
18
+ if (tokens.includes(keyword.toUpperCase())) {
19
+ throw new Error(`Forbidden keyword detected: ${keyword}. Statement blocked for security.`);
20
+ }
21
+ }
22
+ }
23
+ static isSelectOnly(query) {
24
+ const q = query.trim().toUpperCase();
25
+ if (!q.startsWith("SELECT") && !q.startsWith("SHOW") && !q.startsWith("DESCRIBE") && !q.startsWith("EXPLAIN")) {
26
+ throw new Error("Only read-only queries (SELECT, SHOW, DESCRIBE, EXPLAIN) are allowed.");
27
+ }
28
+ this.validate(query, ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "TRUNCATE", "REPLACE"]);
29
+ }
30
+ static isUpdateOnly(query) {
31
+ const q = query.trim().toUpperCase();
32
+ if (!q.startsWith("INSERT") && !q.startsWith("UPDATE") && !q.startsWith("REPLACE")) {
33
+ throw new Error("Only modification queries (INSERT, UPDATE, REPLACE) are allowed.");
34
+ }
35
+ this.validate(query, ["DELETE", "DROP", "ALTER", "TRUNCATE"]);
36
+ }
37
+ static isDeleteOnly(query) {
38
+ const q = query.trim().toUpperCase();
39
+ if (!q.startsWith("DELETE") && !q.startsWith("TRUNCATE")) {
40
+ throw new Error("Only removal queries (DELETE, TRUNCATE) are allowed.");
41
+ }
42
+ this.validate(query, ["DROP", "ALTER"]);
43
+ }
44
+ }
@@ -0,0 +1,82 @@
1
+ export interface DbDriver {
2
+ name: string;
3
+ connect(): Promise<void>;
4
+ disconnect(): Promise<void>;
5
+ listTables(): Promise<TableInfo[]>;
6
+ describeTable(table: string): Promise<ColumnInfo[]>;
7
+ getTableSchema(table: string): Promise<string>;
8
+ getRelationships(tables?: string[]): Promise<Relationship[]>;
9
+ getTableStats(table: string): Promise<TableStats>;
10
+ sampleRows(table: string, limit?: number): Promise<any[]>;
11
+ executeQuery(query: string): Promise<QueryResult>;
12
+ executeUpdate(query: string): Promise<UpdateResult>;
13
+ executeDelete(query: string): Promise<DeleteResult>;
14
+ executeStatement(query: string): Promise<StatementResult>;
15
+ }
16
+ export interface DbConfig {
17
+ driver: 'mysql';
18
+ host: string;
19
+ port: number;
20
+ user: string;
21
+ password: string;
22
+ database: string;
23
+ poolSize?: number;
24
+ enableRunQuery?: boolean;
25
+ enableRunUpdateStatement?: boolean;
26
+ enableRunDeleteStatement?: boolean;
27
+ enableRunStatement?: boolean;
28
+ }
29
+ export interface TableInfo {
30
+ name: string;
31
+ schema: string;
32
+ type: 'BASE TABLE' | 'VIEW';
33
+ comment?: string;
34
+ }
35
+ export interface ColumnInfo {
36
+ name: string;
37
+ type: string;
38
+ fullType: string;
39
+ nullable: boolean;
40
+ defaultValue?: string | null;
41
+ isPrimaryKey: boolean;
42
+ isForeignKey: boolean;
43
+ isAutoIncrement: boolean;
44
+ comment?: string;
45
+ extra?: string;
46
+ enumValues?: string[];
47
+ characterMaxLength?: number;
48
+ numericPrecision?: number;
49
+ numericScale?: number;
50
+ }
51
+ export interface Relationship {
52
+ fromTable: string;
53
+ fromColumn: string;
54
+ toTable: string;
55
+ toColumn: string;
56
+ constraintName: string;
57
+ }
58
+ export interface TableStats {
59
+ rowCount: number;
60
+ dataLength?: number;
61
+ indexLength?: number;
62
+ createTime?: Date;
63
+ updateTime?: Date;
64
+ }
65
+ export interface QueryResult {
66
+ rows: any[];
67
+ fields?: any[];
68
+ }
69
+ export interface UpdateResult {
70
+ affectedRows: number;
71
+ insertId?: number | string;
72
+ message?: string;
73
+ }
74
+ export interface DeleteResult {
75
+ affectedRows: number;
76
+ message?: string;
77
+ }
78
+ export interface StatementResult {
79
+ success: boolean;
80
+ message?: string;
81
+ data?: any;
82
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { MCPServer } from "./mcp-server.js";
2
+ export * from "./db-explorer/index.js";
package/dist/index.js CHANGED
@@ -1 +1,2 @@
1
1
  export { MCPServer } from "./mcp-server.js";
2
+ export * from "./db-explorer/index.js";
@@ -1,4 +1,19 @@
1
1
  import { AuthContext } from "./api-explorer/auth/index.js";
2
+ import type { DbConfig } from "./db-explorer/index.js";
3
+ /**
4
+ * The main MCP Server implementation that coordinates the OpenAPI parser,
5
+ * tool generation, and API execution.
6
+ *
7
+ * It registers meta-tools that allow LLMs to:
8
+ * 1. Discover API structure (tags, endpoints, schemas).
9
+ * 2. Execute calls to any endpoint with dynamic authentication.
10
+ * 3. Impersonate users via the set_identity tool.
11
+ */
12
+ export interface MCPServerOptions {
13
+ specPath: string;
14
+ authContext?: AuthContext;
15
+ dbConfig?: DbConfig;
16
+ }
2
17
  /**
3
18
  * The main MCP Server implementation that coordinates the OpenAPI parser,
4
19
  * tool generation, and API execution.
@@ -9,17 +24,18 @@ import { AuthContext } from "./api-explorer/auth/index.js";
9
24
  * 3. Impersonate users via the set_identity tool.
10
25
  */
11
26
  export declare class MCPServer {
12
- private specPath;
13
27
  private server;
14
28
  private parser;
15
29
  private toolGenerator;
16
30
  private apiExecutor;
17
31
  private authContext;
32
+ private dbExecutor?;
33
+ private dbToolGenerator?;
34
+ private specPath;
18
35
  /**
19
- * @param specPath - Path to the OpenAPI specification file.
20
- * @param authContext - Optional custom AuthContext. If not provided, creates one from environment variables.
36
+ * @param options - Configuration options for the MCP Server.
21
37
  */
22
- constructor(specPath: string, authContext?: AuthContext);
38
+ constructor(options: MCPServerOptions);
23
39
  private initParser;
24
40
  private registerTools;
25
41
  /**
@@ -5,6 +5,8 @@ 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
10
  /**
9
11
  * The main MCP Server implementation that coordinates the OpenAPI parser,
10
12
  * tool generation, and API execution.
@@ -15,22 +17,28 @@ import { createAuthContextFromEnv } from "./api-explorer/auth/index.js";
15
17
  * 3. Impersonate users via the set_identity tool.
16
18
  */
17
19
  export class MCPServer {
18
- specPath;
19
20
  server;
20
21
  parser;
21
22
  toolGenerator;
22
23
  apiExecutor;
23
24
  authContext;
25
+ dbExecutor;
26
+ dbToolGenerator;
27
+ specPath;
24
28
  /**
25
- * @param specPath - Path to the OpenAPI specification file.
26
- * @param authContext - Optional custom AuthContext. If not provided, creates one from environment variables.
29
+ * @param options - Configuration options for the MCP Server.
27
30
  */
28
- constructor(specPath, authContext) {
29
- this.specPath = specPath;
31
+ constructor(options) {
32
+ this.specPath = options.specPath;
30
33
  this.parser = new OpenAPIParser();
31
34
  this.toolGenerator = new ToolGenerator(this.parser);
32
- this.authContext = authContext || createAuthContextFromEnv();
35
+ this.authContext = options.authContext || createAuthContextFromEnv();
33
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);
41
+ }
34
42
  this.server = new McpServer({
35
43
  name: "project-mcp-server",
36
44
  version: "1.0.0",
@@ -40,6 +48,9 @@ export class MCPServer {
40
48
  try {
41
49
  await this.parser.loadSpec(this.specPath);
42
50
  console.error(`Loaded OpenAPI spec from ${this.specPath}`);
51
+ if (this.dbExecutor) {
52
+ await this.dbExecutor.connect();
53
+ }
43
54
  this.registerTools();
44
55
  }
45
56
  catch (error) {
@@ -56,7 +67,7 @@ export class MCPServer {
56
67
  }, async (args) => {
57
68
  try {
58
69
  let result;
59
- if (name === "call_endpoint") {
70
+ if (name === "api_call_endpoint") {
60
71
  result = await this.apiExecutor.callEndpoint(args);
61
72
  }
62
73
  else {
@@ -74,6 +85,29 @@ export class MCPServer {
74
85
  }
75
86
  });
76
87
  });
88
+ if (this.dbToolGenerator) {
89
+ const dbDefinitions = this.dbToolGenerator.getToolDefinitions();
90
+ Object.entries(dbDefinitions).forEach(([name, def]) => {
91
+ const toolDef = def;
92
+ this.server.registerTool(name, {
93
+ description: toolDef.description,
94
+ inputSchema: toolDef.inputSchema,
95
+ }, async (args) => {
96
+ try {
97
+ const result = await this.dbToolGenerator.handleToolCall(name, args);
98
+ return {
99
+ content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
100
+ };
101
+ }
102
+ catch (error) {
103
+ return {
104
+ content: [{ type: "text", text: `Error: ${error.message}` }],
105
+ isError: true,
106
+ };
107
+ }
108
+ });
109
+ });
110
+ }
77
111
  this.server.registerTool("set_identity", {
78
112
  description: "Set the identity (identifier) for future API requests. Requires an 'identity' auth strategy to be active.",
79
113
  inputSchema: z.object({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@williamp29/project-mcp-server",
3
- "version": "1.1.0",
3
+ "version": "2.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",
@@ -29,19 +29,27 @@
29
29
  "types": "./dist/api-explorer/index.d.ts",
30
30
  "import": "./dist/api-explorer/index.js",
31
31
  "default": "./dist/api-explorer/index.js"
32
+ },
33
+ "./db-explorer": {
34
+ "types": "./dist/db-explorer/index.d.ts",
35
+ "import": "./dist/db-explorer/index.js",
36
+ "default": "./dist/db-explorer/index.js"
32
37
  }
33
38
  },
34
39
  "scripts": {
35
40
  "build": "tsc",
36
41
  "start": "node dist/cli.js",
37
42
  "dev": "ts-node src/cli.ts",
38
- "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"
43
+ "inspector": "npx @modelcontextprotocol/inspector node dist/cli.js",
44
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
45
+ "publish": "npm publish --access public"
39
46
  },
40
47
  "dependencies": {
41
48
  "@apidevtools/swagger-parser": "^12.1.0",
42
49
  "@modelcontextprotocol/sdk": "^1.25.2",
43
50
  "axios": "^1.13.2",
44
- "dotenv": "^17.2.3",
51
+ "dotenv": "^16.4.5",
52
+ "mysql2": "^3.16.1",
45
53
  "openapi-types": "^12.1.3",
46
54
  "zod": "^4.3.5"
47
55
  },