@abhishek8380/qconsul-mcp-server 1.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.
Files changed (4) hide show
  1. package/.env.example +12 -0
  2. package/README.md +433 -0
  3. package/package.json +40 -0
  4. package/server.js +500 -0
package/.env.example ADDED
@@ -0,0 +1,12 @@
1
+ # QConsul (Consul) Configuration
2
+ # Copy this file to .env and fill in your credentials
3
+
4
+ # Your Consul instance URL
5
+ CONSUL_URL=https://qconsul.p19.eng.sjc01.qualys.com
6
+
7
+ # Consul authentication credentials (HTTP Basic Auth)
8
+ CONSUL_USERNAME=admin
9
+ CONSUL_PASSWORD=admin
10
+
11
+ # Default datacenter (e.g., eng-sjc01-p19)
12
+ CONSUL_DATACENTER=eng-sjc01-p19
package/README.md ADDED
@@ -0,0 +1,433 @@
1
+ # Qualys QConsul MCP Server
2
+
3
+ A Model Context Protocol (MCP) server for HashiCorp Consul KV store operations. This server enables AI assistants to interact with Consul's key-value store through a standardized interface, allowing you to read, write, update, and delete configuration values directly from your IDE.
4
+
5
+ **Built for Qualys infrastructure teams** to manage Consul KV configurations seamlessly.
6
+
7
+ ## Features
8
+
9
+ - 🔍 **Search and list keys** - Discover keys with prefix matching and search
10
+ - 📖 **Read key values** - Get individual or recursive key-value pairs
11
+ - ✏️ **Create/Update keys** - Set or modify key-value pairs
12
+ - 🗑️ **Delete keys** - Remove single keys or entire prefixes recursively
13
+ - 🔐 **HTTP Basic Authentication** - Secure access with username/password
14
+ - 🧠 **Dual naming** - Every tool available under both `*` and `qconsul_*` aliases
15
+ - 🚀 **Zero installation** - Use with npx in your MCP client configuration
16
+
17
+ ## Use Case
18
+
19
+ This MCP server is designed for teams using Consul KV store for configuration management. It allows you to:
20
+
21
+ - **Update configuration files** directly from your IDE without switching to the Consul UI
22
+ - **Search and discover** configuration keys across your infrastructure
23
+ - **Bulk operations** on configuration hierarchies
24
+ - **Version control integration** - Make config changes alongside code changes
25
+
26
+ Perfect for DevOps teams managing microservices configuration in Consul.
27
+
28
+ ## Installation
29
+
30
+ No installation required! Use with npx in your MCP client configuration.
31
+
32
+ ## Configuration for Windsurf
33
+
34
+ Add this to your Windsurf MCP configuration file (`~/.codeium/windsurf/mcp_config.json`):
35
+
36
+ ```json
37
+ {
38
+ "mcpServers": {
39
+ "qconsul": {
40
+ "command": "npx",
41
+ "args": ["-y", "@abhishek8380/qconsul-mcp-server"],
42
+ "env": {
43
+ "CONSUL_URL": "https://qconsul.p19.eng.sjc01.qualys.com",
44
+ "CONSUL_USERNAME": "your-username",
45
+ "CONSUL_PASSWORD": "your-password",
46
+ "CONSUL_DATACENTER": "eng-sjc01-p19"
47
+ }
48
+ }
49
+ }
50
+ }
51
+ ```
52
+
53
+ ### Environment Variables
54
+
55
+ - `CONSUL_URL` - Your Consul instance URL (e.g., `https://qconsul.p19.eng.sjc01.qualys.com`)
56
+ - `CONSUL_USERNAME` - HTTP Basic Auth username (required)
57
+ - `CONSUL_PASSWORD` - HTTP Basic Auth password (required)
58
+ - `CONSUL_DATACENTER` - Default datacenter to use (e.g., `eng-sjc01-p19`)
59
+
60
+ ## Configuration for Claude Desktop
61
+
62
+ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
63
+
64
+ ```json
65
+ {
66
+ "mcpServers": {
67
+ "qconsul": {
68
+ "command": "npx",
69
+ "args": ["-y", "@abhishek8380/qconsul-mcp-server"],
70
+ "env": {
71
+ "CONSUL_URL": "https://qconsul.p19.eng.sjc01.qualys.com",
72
+ "CONSUL_USERNAME": "your-username",
73
+ "CONSUL_PASSWORD": "your-password",
74
+ "CONSUL_DATACENTER": "eng-sjc01-p19"
75
+ }
76
+ }
77
+ }
78
+ }
79
+ ```
80
+
81
+ ## Available Tools
82
+
83
+ Every tool below can be called with either its standard name or the `qconsul_*` alias shown in parentheses.
84
+
85
+ ### Read Operations
86
+
87
+ #### **get_key** (`qconsul_get_key`)
88
+ Retrieve a value from Consul KV store by key path.
89
+
90
+ **Parameters:**
91
+ - `key` (string, required) - The key path to retrieve (e.g., `cloudview/config/setting`)
92
+ - `raw` (boolean, optional) - Return raw value without metadata (default: `false`)
93
+ - `datacenter` (string, optional) - Datacenter to query
94
+
95
+ **Example:**
96
+ ```javascript
97
+ {
98
+ "key": "cloudview/config/database",
99
+ "raw": false
100
+ }
101
+ ```
102
+
103
+ #### **list_keys** (`qconsul_list_keys`)
104
+ List all keys under a prefix in Consul KV store.
105
+
106
+ **Parameters:**
107
+ - `prefix` (string, optional) - Key prefix to list (empty for all keys)
108
+ - `separator` (string, optional) - Separator for grouping keys (e.g., `/`)
109
+ - `datacenter` (string, optional) - Datacenter to query
110
+
111
+ **Example:**
112
+ ```javascript
113
+ {
114
+ "prefix": "cloudview/",
115
+ "separator": "/"
116
+ }
117
+ ```
118
+
119
+ #### **get_keys_recursive** (`qconsul_get_keys_recursive`)
120
+ Retrieve all key-value pairs under a prefix recursively.
121
+
122
+ **Parameters:**
123
+ - `prefix` (string, optional) - Key prefix to retrieve (empty for all keys)
124
+ - `datacenter` (string, optional) - Datacenter to query
125
+
126
+ **Example:**
127
+ ```javascript
128
+ {
129
+ "prefix": "cloudview/config/"
130
+ }
131
+ ```
132
+
133
+ #### **search_keys** (`qconsul_search_keys`)
134
+ Search for keys matching a pattern in Consul KV store.
135
+
136
+ **Parameters:**
137
+ - `searchTerm` (string, required) - Search term to find in key names
138
+ - `prefix` (string, optional) - Optional prefix to narrow search scope
139
+ - `datacenter` (string, optional) - Datacenter to query
140
+
141
+ **Example:**
142
+ ```javascript
143
+ {
144
+ "searchTerm": "database",
145
+ "prefix": "cloudview/"
146
+ }
147
+ ```
148
+
149
+ ### Write Operations
150
+
151
+ #### **put_key** (`qconsul_put_key`)
152
+ Create or update a key-value pair in Consul KV store.
153
+
154
+ **Parameters:**
155
+ - `key` (string, required) - The key path to create/update (e.g., `cloudview/config/setting`)
156
+ - `value` (string, required) - The value to store
157
+ - `datacenter` (string, optional) - Datacenter to use
158
+ - `flags` (number, optional) - Optional flags for the key (default: `0`)
159
+
160
+ **Example:**
161
+ ```javascript
162
+ {
163
+ "key": "cloudview/config/timeout",
164
+ "value": "30"
165
+ }
166
+ ```
167
+
168
+ ### Delete Operations
169
+
170
+ #### **delete_key** (`qconsul_delete_key`)
171
+ Delete a key or keys from Consul KV store.
172
+
173
+ **Parameters:**
174
+ - `key` (string, required) - The key path to delete
175
+ - `recurse` (boolean, optional) - Delete all keys with this prefix (default: `false`)
176
+ - `datacenter` (string, optional) - Datacenter to use
177
+
178
+ **Example:**
179
+ ```javascript
180
+ {
181
+ "key": "cloudview/config/old-setting",
182
+ "recurse": false
183
+ }
184
+ ```
185
+
186
+ ## How to Use - CRUD Operations Guide
187
+
188
+ ### 1. **CREATE** - Add New Configuration
189
+
190
+ **Create a single key:**
191
+ ```
192
+ Ask: "Create a new key cloudview/config/new-feature with value 'enabled'"
193
+ ```
194
+
195
+ **Create multiple keys (bulk upload):**
196
+ ```
197
+ Ask: "Create these keys under cloudview/myapp/:
198
+ - config.yml with database settings
199
+ - kafka-client.yml with broker configs
200
+ - application.properties with app settings"
201
+ ```
202
+
203
+ **What happens:** The MCP server uses `put_key` to create new entries in Consul KV store.
204
+
205
+ ---
206
+
207
+ ### 2. **READ** - Retrieve Configuration
208
+
209
+ **Read a single key:**
210
+ ```
211
+ Ask: "Get the value of cloudview/config/database"
212
+ Ask: "Show me cloudview/gcp-service/application.yml"
213
+ ```
214
+
215
+ **List all keys under a prefix:**
216
+ ```
217
+ Ask: "List all keys under cloudview/"
218
+ Ask: "Show me all configuration files in cloudview/myapp/"
219
+ ```
220
+
221
+ **Search for specific keys:**
222
+ ```
223
+ Ask: "Search for keys containing 'database' in cloudview"
224
+ Ask: "Find all kafka configuration keys"
225
+ ```
226
+
227
+ **Get all keys recursively with values:**
228
+ ```
229
+ Ask: "Get all key-value pairs under cloudview/config/"
230
+ ```
231
+
232
+ **What happens:** The MCP server uses `get_key`, `list_keys`, `search_keys`, or `get_keys_recursive` to fetch data.
233
+
234
+ ---
235
+
236
+ ### 3. **UPDATE** - Modify Existing Configuration
237
+
238
+ **Update a single value:**
239
+ ```
240
+ Ask: "Update cloudview/config/timeout to 60"
241
+ Ask: "Change the version in cloudview/gcp-service/application.yml to 2.8.0"
242
+ ```
243
+
244
+ **Update entire file:**
245
+ ```
246
+ Ask: "Update cloudview/config/database.yml with this content:
247
+ host: db.example.com
248
+ port: 5432
249
+ username: dbuser"
250
+ ```
251
+
252
+ **What happens:** The MCP server:
253
+ 1. Reads the current value (if needed)
254
+ 2. Modifies the specific field or replaces entire content
255
+ 3. Uses `put_key` to update the value in Consul
256
+
257
+ ---
258
+
259
+ ### 4. **DELETE** - Remove Configuration
260
+
261
+ **Delete a single key:**
262
+ ```
263
+ Ask: "Delete the key cloudview/config/deprecated-setting"
264
+ ```
265
+
266
+ **Delete all keys under a prefix (recursive):**
267
+ ```
268
+ Ask: "Delete all keys under cloudview/old-service/ recursively"
269
+ ```
270
+
271
+ **What happens:** The MCP server uses `delete_key` with optional `recurse` parameter.
272
+
273
+ ---
274
+
275
+ ## Complete Example Workflow
276
+
277
+ ### Scenario: Managing a new microservice configuration
278
+
279
+ **Step 1: Discover existing structure**
280
+ ```
281
+ Ask: "List all keys under cloudview/"
282
+ ```
283
+
284
+ **Step 2: Create new service configuration**
285
+ ```
286
+ Ask: "Create cloudview/my-service/application.yml with:
287
+ server:
288
+ port: 8080
289
+ database:
290
+ url: jdbc:postgresql://localhost:5432/mydb"
291
+ ```
292
+
293
+ **Step 3: Verify creation**
294
+ ```
295
+ Ask: "Get the value of cloudview/my-service/application.yml"
296
+ ```
297
+
298
+ **Step 4: Update a specific value**
299
+ ```
300
+ Ask: "Update the port in cloudview/my-service/application.yml to 9090"
301
+ ```
302
+
303
+ **Step 5: Add more configuration files**
304
+ ```
305
+ Ask: "Create cloudview/my-service/kafka-client.yml with broker configs"
306
+ ```
307
+
308
+ **Step 6: List all service configs**
309
+ ```
310
+ Ask: "List all keys under cloudview/my-service/"
311
+ ```
312
+
313
+ **Step 7: Clean up (if needed)**
314
+ ```
315
+ Ask: "Delete cloudview/my-service/old-config.yml"
316
+ ```
317
+
318
+ ## Authentication
319
+
320
+ This server uses **HTTP Basic Authentication** to connect to your Consul instance. The credentials are passed via environment variables:
321
+
322
+ - Username and password are base64-encoded and sent in the `Authorization` header
323
+ - Ensure your Consul instance is configured to accept Basic Auth
324
+ - For production use, consider using ACL tokens instead of basic auth
325
+
326
+ ## Consul KV Hierarchy
327
+
328
+ Consul KV uses a hierarchical key structure with `/` as the separator:
329
+
330
+ ```
331
+ cloudview/
332
+ ├── config/
333
+ │ ├── database
334
+ │ ├── timeout
335
+ │ └── api-key
336
+ ├── features/
337
+ │ ├── feature-a
338
+ │ └── feature-b
339
+ └── metadata/
340
+ └── version
341
+ ```
342
+
343
+ Keys are case-sensitive and can contain any UTF-8 characters.
344
+
345
+ ## Local Development
346
+
347
+ 1. Clone the repository
348
+ 2. Install dependencies:
349
+ ```bash
350
+ npm install
351
+ ```
352
+
353
+ 3. Create `.env` file:
354
+ ```bash
355
+ cp .env.example .env
356
+ # Edit .env with your credentials
357
+ ```
358
+
359
+ 4. Run the server:
360
+ ```bash
361
+ npm start
362
+ ```
363
+
364
+ ## Testing the Server
365
+
366
+ You can test the server locally using the MCP Inspector or by configuring it in Windsurf/Claude Desktop.
367
+
368
+ ### Example Commands
369
+
370
+ ```bash
371
+ # List all keys
372
+ curl -u username:password https://qconsul.p19.eng.sjc01.qualys.com/v1/kv/?keys=true
373
+
374
+ # Get a specific key
375
+ curl -u username:password https://qconsul.p19.eng.sjc01.qualys.com/v1/kv/cloudview/config/setting
376
+
377
+ # Put a key
378
+ curl -u username:password -X PUT -d "value" https://qconsul.p19.eng.sjc01.qualys.com/v1/kv/cloudview/config/setting
379
+
380
+ # Delete a key
381
+ curl -u username:password -X DELETE https://qconsul.p19.eng.sjc01.qualys.com/v1/kv/cloudview/config/setting
382
+ ```
383
+
384
+ ## Security Considerations
385
+
386
+ - **Never commit credentials** - Use environment variables or secure secret management
387
+ - **Use HTTPS** - Always connect to Consul over HTTPS in production
388
+ - **ACL Tokens** - Consider using Consul ACL tokens instead of basic auth for production
389
+ - **Least Privilege** - Grant only necessary permissions to the MCP server
390
+
391
+ ## Troubleshooting
392
+
393
+ ### Connection Issues
394
+ - Verify `CONSUL_URL` is correct and accessible
395
+ - Check that your credentials are valid
396
+ - Ensure your network allows access to the Consul instance
397
+
398
+ ### Authentication Errors
399
+ - Confirm username and password are correct
400
+ - Check if Consul requires ACL tokens instead of basic auth
401
+
402
+ ### Key Not Found
403
+ - Verify the key path is correct (case-sensitive)
404
+ - Use `list_keys` to discover available keys
405
+ - Check the datacenter parameter if using multiple datacenters
406
+
407
+ ## API Reference
408
+
409
+ This server implements the Consul KV HTTP API:
410
+ - [Consul KV Store API Documentation](https://developer.hashicorp.com/consul/api-docs/kv)
411
+ - [Consul KV CLI Reference](https://developer.hashicorp.com/consul/commands/kv)
412
+
413
+ ## License
414
+
415
+ MIT
416
+
417
+ ## Author
418
+
419
+ Abhishek Bhadane
420
+
421
+ ## Package
422
+
423
+ https://www.npmjs.com/package/@abhishek8380/qconsul-mcp-server
424
+
425
+ ## Contributing
426
+
427
+ Contributions are welcome! Please feel free to submit a Pull Request.
428
+
429
+ ## Support
430
+
431
+ For issues and questions:
432
+ - GitHub Issues: [Create an issue](https://github.com/yourusername/qconsul-mcp-server/issues)
433
+ - Documentation: [Consul KV Store](https://developer.hashicorp.com/consul/docs/automate/kv)
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@abhishek8380/qconsul-mcp-server",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "description": "MCP server for Consul KV store operations (get, put, delete, list keys)",
6
+ "main": "server.js",
7
+ "bin": {
8
+ "qconsul-mcp": "./server.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node server.js"
12
+ },
13
+ "keywords": [
14
+ "mcp",
15
+ "consul",
16
+ "qconsul",
17
+ "kv",
18
+ "key-value",
19
+ "stdio",
20
+ "model-context-protocol",
21
+ "windsurf",
22
+ "hashicorp"
23
+ ],
24
+ "author": "Abhishek Bhadane",
25
+ "license": "MIT",
26
+ "dependencies": {
27
+ "@modelcontextprotocol/sdk": "^1.0.0",
28
+ "node-fetch": "^3.3.2",
29
+ "dotenv": "^16.3.1",
30
+ "zod": "^3.22.4"
31
+ },
32
+ "engines": {
33
+ "node": ">=18.0.0"
34
+ },
35
+ "files": [
36
+ "server.js",
37
+ "README.md",
38
+ ".env.example"
39
+ ]
40
+ }
package/server.js ADDED
@@ -0,0 +1,500 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
+ import fetch from "node-fetch";
6
+ import { z } from "zod";
7
+ import dotenv from "dotenv";
8
+ import https from "https";
9
+
10
+ dotenv.config();
11
+
12
+ const httpsAgent = new https.Agent({
13
+ rejectUnauthorized: false
14
+ });
15
+
16
+ const CONSUL_URL = process.env.CONSUL_URL
17
+ const CONSUL_USERNAME = process.env.CONSUL_USERNAME
18
+ const CONSUL_PASSWORD = process.env.CONSUL_PASSWORD
19
+ const CONSUL_DATACENTER = process.env.CONSUL_DATACENTER;
20
+
21
+ console.error(`🔐 Consul URL: ${CONSUL_URL}`);
22
+ console.error(`🔐 Datacenter: ${CONSUL_DATACENTER}`);
23
+ console.error(`🔐 Username: ${CONSUL_USERNAME}`);
24
+
25
+ async function makeAuthenticatedRequest(url, options = {}) {
26
+ const authString = Buffer.from(`${CONSUL_USERNAME}:${CONSUL_PASSWORD}`).toString('base64');
27
+
28
+ const defaultHeaders = {
29
+ "Accept": "application/json",
30
+ "Content-Type": "application/json",
31
+ "Authorization": `Basic ${authString}`,
32
+ };
33
+
34
+ const requestOptions = {
35
+ ...options,
36
+ agent: httpsAgent,
37
+ headers: {
38
+ ...defaultHeaders,
39
+ ...options.headers
40
+ }
41
+ };
42
+
43
+ const response = await fetch(url, requestOptions);
44
+ return response;
45
+ }
46
+
47
+ const server = new McpServer({
48
+ name: "qconsul-mcp-server",
49
+ version: "1.0.0",
50
+ });
51
+
52
+ const onboardingTip = [
53
+ "💡 QConsul (Consul KV) tip:",
54
+ "Use list_keys to discover available keys before reading or updating.",
55
+ "Keys are hierarchical (e.g., cloudview/config/setting)."
56
+ ].join("\n");
57
+
58
+ console.error(onboardingTip);
59
+
60
+ function registerQConsulTool(toolNames, config, handler) {
61
+ const names = Array.isArray(toolNames) ? toolNames : [toolNames];
62
+ names.forEach((toolName) => {
63
+ server.registerTool(toolName, { ...config }, handler);
64
+ });
65
+ }
66
+
67
+ registerQConsulTool([
68
+ "get_key",
69
+ "qconsul_get_key"
70
+ ],
71
+ {
72
+ title: "Get Consul KV Key",
73
+ description: "Retrieve a value from Consul KV store by key path",
74
+ inputSchema: z.object({
75
+ key: z.string().describe("The key path to retrieve (e.g., cloudview/config/setting)"),
76
+ raw: z.boolean().optional().default(false).describe("Return raw value without metadata (default: false)"),
77
+ datacenter: z.string().optional().describe("Datacenter to query (optional)")
78
+ }),
79
+ },
80
+ async ({ key, raw, datacenter }) => {
81
+ console.error(`📖 Getting key: ${key}`);
82
+
83
+ try {
84
+ const dc = datacenter || CONSUL_DATACENTER;
85
+ const rawParam = raw ? "&raw=true" : "";
86
+ const url = `${CONSUL_URL}/v1/kv/${key}?dc=${dc}${rawParam}`;
87
+
88
+ const response = await makeAuthenticatedRequest(url);
89
+
90
+ if (response.status === 404) {
91
+ return {
92
+ content: [
93
+ {
94
+ type: "text",
95
+ text: `❌ Key not found: ${key}`,
96
+ },
97
+ ],
98
+ };
99
+ }
100
+
101
+ if (!response.ok) {
102
+ throw new Error(`HTTP error! status: ${response.status}`);
103
+ }
104
+
105
+ if (raw) {
106
+ const rawValue = await response.text();
107
+ return {
108
+ content: [
109
+ {
110
+ type: "text",
111
+ text: rawValue,
112
+ },
113
+ ],
114
+ };
115
+ }
116
+
117
+ const data = await response.json();
118
+ const kvData = data[0];
119
+
120
+ const decodedValue = kvData.Value ? Buffer.from(kvData.Value, 'base64').toString('utf-8') : null;
121
+
122
+ const keyInfo = {
123
+ key: kvData.Key,
124
+ value: decodedValue,
125
+ createIndex: kvData.CreateIndex,
126
+ modifyIndex: kvData.ModifyIndex,
127
+ lockIndex: kvData.LockIndex,
128
+ flags: kvData.Flags
129
+ };
130
+
131
+ return {
132
+ content: [
133
+ {
134
+ type: "text",
135
+ text: JSON.stringify(keyInfo, null, 2),
136
+ },
137
+ ],
138
+ };
139
+ } catch (error) {
140
+ console.error("❌ Error getting key:", error);
141
+ return {
142
+ content: [
143
+ {
144
+ type: "text",
145
+ text: `Error getting key: ${error.message}`,
146
+ },
147
+ ],
148
+ };
149
+ }
150
+ }
151
+ );
152
+
153
+ registerQConsulTool([
154
+ "put_key",
155
+ "qconsul_put_key"
156
+ ],
157
+ {
158
+ title: "Put/Update Consul KV Key",
159
+ description: "Create or update a key-value pair in Consul KV store",
160
+ inputSchema: z.object({
161
+ key: z.string().describe("The key path to create/update (e.g., cloudview/config/setting)"),
162
+ value: z.string().describe("The value to store"),
163
+ datacenter: z.string().optional().describe("Datacenter to use (optional)"),
164
+ flags: z.number().optional().describe("Optional flags for the key (default: 0)")
165
+ }),
166
+ },
167
+ async ({ key, value, datacenter, flags }) => {
168
+ console.error(`✏️ Putting key: ${key}`);
169
+
170
+ try {
171
+ const dc = datacenter || CONSUL_DATACENTER;
172
+ const flagsParam = flags !== undefined ? `&flags=${flags}` : "";
173
+ const url = `${CONSUL_URL}/v1/kv/${key}?dc=${dc}${flagsParam}`;
174
+
175
+ const response = await makeAuthenticatedRequest(url, {
176
+ method: "PUT",
177
+ body: value,
178
+ headers: {
179
+ "Content-Type": "text/plain"
180
+ }
181
+ });
182
+
183
+ if (!response.ok) {
184
+ const errorText = await response.text();
185
+ throw new Error(`HTTP error! status: ${response.status}, details: ${errorText}`);
186
+ }
187
+
188
+ const success = await response.json();
189
+
190
+ if (success) {
191
+ return {
192
+ content: [
193
+ {
194
+ type: "text",
195
+ text: `✅ Key updated successfully!\n\nKey: ${key}\nValue: ${value}\nDatacenter: ${dc}`,
196
+ },
197
+ ],
198
+ };
199
+ } else {
200
+ return {
201
+ content: [
202
+ {
203
+ type: "text",
204
+ text: `⚠️ Key update returned false. This may indicate a CAS failure or other issue.`,
205
+ },
206
+ ],
207
+ };
208
+ }
209
+ } catch (error) {
210
+ console.error("❌ Error putting key:", error);
211
+ return {
212
+ content: [
213
+ {
214
+ type: "text",
215
+ text: `Error putting key: ${error.message}`,
216
+ },
217
+ ],
218
+ };
219
+ }
220
+ }
221
+ );
222
+
223
+ registerQConsulTool([
224
+ "delete_key",
225
+ "qconsul_delete_key"
226
+ ],
227
+ {
228
+ title: "Delete Consul KV Key",
229
+ description: "Delete a key or keys from Consul KV store",
230
+ inputSchema: z.object({
231
+ key: z.string().describe("The key path to delete"),
232
+ recurse: z.boolean().optional().default(false).describe("Delete all keys with this prefix (default: false)"),
233
+ datacenter: z.string().optional().describe("Datacenter to use (optional)")
234
+ }),
235
+ },
236
+ async ({ key, recurse, datacenter }) => {
237
+ console.error(`🗑️ Deleting key: ${key}${recurse ? ' (recursive)' : ''}`);
238
+
239
+ try {
240
+ const dc = datacenter || CONSUL_DATACENTER;
241
+ const recurseParam = recurse ? "&recurse=true" : "";
242
+ const url = `${CONSUL_URL}/v1/kv/${key}?dc=${dc}${recurseParam}`;
243
+
244
+ const response = await makeAuthenticatedRequest(url, {
245
+ method: "DELETE"
246
+ });
247
+
248
+ if (!response.ok) {
249
+ throw new Error(`HTTP error! status: ${response.status}`);
250
+ }
251
+
252
+ const success = await response.json();
253
+
254
+ if (success) {
255
+ return {
256
+ content: [
257
+ {
258
+ type: "text",
259
+ text: `✅ Key deleted successfully!\n\nKey: ${key}${recurse ? '\nMode: Recursive (all keys with this prefix)' : ''}`,
260
+ },
261
+ ],
262
+ };
263
+ } else {
264
+ return {
265
+ content: [
266
+ {
267
+ type: "text",
268
+ text: `⚠️ Key deletion returned false. Key may not exist.`,
269
+ },
270
+ ],
271
+ };
272
+ }
273
+ } catch (error) {
274
+ console.error("❌ Error deleting key:", error);
275
+ return {
276
+ content: [
277
+ {
278
+ type: "text",
279
+ text: `Error deleting key: ${error.message}`,
280
+ },
281
+ ],
282
+ };
283
+ }
284
+ }
285
+ );
286
+
287
+ registerQConsulTool([
288
+ "list_keys",
289
+ "qconsul_list_keys"
290
+ ],
291
+ {
292
+ title: "List Consul KV Keys",
293
+ description: "List all keys under a prefix in Consul KV store",
294
+ inputSchema: z.object({
295
+ prefix: z.string().optional().default("").describe("Key prefix to list (empty for all keys)"),
296
+ separator: z.string().optional().describe("Separator for grouping keys (e.g., '/')"),
297
+ datacenter: z.string().optional().describe("Datacenter to query (optional)")
298
+ }),
299
+ },
300
+ async ({ prefix, separator, datacenter }) => {
301
+ console.error(`📋 Listing keys with prefix: ${prefix || '(all)'}`);
302
+
303
+ try {
304
+ const dc = datacenter || CONSUL_DATACENTER;
305
+ const separatorParam = separator ? `&separator=${encodeURIComponent(separator)}` : "";
306
+ const url = `${CONSUL_URL}/v1/kv/${prefix}?dc=${dc}&keys=true${separatorParam}`;
307
+
308
+ const response = await makeAuthenticatedRequest(url);
309
+
310
+ if (response.status === 404) {
311
+ return {
312
+ content: [
313
+ {
314
+ type: "text",
315
+ text: `No keys found with prefix: ${prefix || '(root)'}`,
316
+ },
317
+ ],
318
+ };
319
+ }
320
+
321
+ if (!response.ok) {
322
+ throw new Error(`HTTP error! status: ${response.status}`);
323
+ }
324
+
325
+ const keys = await response.json();
326
+
327
+ return {
328
+ content: [
329
+ {
330
+ type: "text",
331
+ text: `Found ${keys.length} key(s)${prefix ? ` with prefix "${prefix}"` : ''}:\n\n${JSON.stringify(keys, null, 2)}`,
332
+ },
333
+ ],
334
+ };
335
+ } catch (error) {
336
+ console.error("❌ Error listing keys:", error);
337
+ return {
338
+ content: [
339
+ {
340
+ type: "text",
341
+ text: `Error listing keys: ${error.message}`,
342
+ },
343
+ ],
344
+ };
345
+ }
346
+ }
347
+ );
348
+
349
+ registerQConsulTool([
350
+ "get_keys_recursive",
351
+ "qconsul_get_keys_recursive"
352
+ ],
353
+ {
354
+ title: "Get All Keys and Values Recursively",
355
+ description: "Retrieve all key-value pairs under a prefix (recursive)",
356
+ inputSchema: z.object({
357
+ prefix: z.string().optional().default("").describe("Key prefix to retrieve (empty for all keys)"),
358
+ datacenter: z.string().optional().describe("Datacenter to query (optional)")
359
+ }),
360
+ },
361
+ async ({ prefix, datacenter }) => {
362
+ console.error(`📦 Getting all keys recursively with prefix: ${prefix || '(all)'}`);
363
+
364
+ try {
365
+ const dc = datacenter || CONSUL_DATACENTER;
366
+ const url = `${CONSUL_URL}/v1/kv/${prefix}?dc=${dc}&recurse=true`;
367
+
368
+ const response = await makeAuthenticatedRequest(url);
369
+
370
+ if (response.status === 404) {
371
+ return {
372
+ content: [
373
+ {
374
+ type: "text",
375
+ text: `No keys found with prefix: ${prefix || '(root)'}`,
376
+ },
377
+ ],
378
+ };
379
+ }
380
+
381
+ if (!response.ok) {
382
+ throw new Error(`HTTP error! status: ${response.status}`);
383
+ }
384
+
385
+ const data = await response.json();
386
+
387
+ const kvPairs = data.map(item => ({
388
+ key: item.Key,
389
+ value: item.Value ? Buffer.from(item.Value, 'base64').toString('utf-8') : null,
390
+ createIndex: item.CreateIndex,
391
+ modifyIndex: item.ModifyIndex,
392
+ flags: item.Flags
393
+ }));
394
+
395
+ return {
396
+ content: [
397
+ {
398
+ type: "text",
399
+ text: `Found ${kvPairs.length} key-value pair(s)${prefix ? ` with prefix "${prefix}"` : ''}:\n\n${JSON.stringify(kvPairs, null, 2)}`,
400
+ },
401
+ ],
402
+ };
403
+ } catch (error) {
404
+ console.error("❌ Error getting keys recursively:", error);
405
+ return {
406
+ content: [
407
+ {
408
+ type: "text",
409
+ text: `Error getting keys recursively: ${error.message}`,
410
+ },
411
+ ],
412
+ };
413
+ }
414
+ }
415
+ );
416
+
417
+ registerQConsulTool([
418
+ "search_keys",
419
+ "qconsul_search_keys"
420
+ ],
421
+ {
422
+ title: "Search Consul KV Keys",
423
+ description: "Search for keys matching a pattern in Consul KV store",
424
+ inputSchema: z.object({
425
+ searchTerm: z.string().describe("Search term to find in key names"),
426
+ prefix: z.string().optional().default("").describe("Optional prefix to narrow search scope"),
427
+ datacenter: z.string().optional().describe("Datacenter to query (optional)")
428
+ }),
429
+ },
430
+ async ({ searchTerm, prefix, datacenter }) => {
431
+ console.error(`🔍 Searching for keys matching: ${searchTerm}`);
432
+
433
+ try {
434
+ const dc = datacenter || CONSUL_DATACENTER;
435
+ const url = `${CONSUL_URL}/v1/kv/${prefix}?dc=${dc}&keys=true`;
436
+
437
+ const response = await makeAuthenticatedRequest(url);
438
+
439
+ if (response.status === 404) {
440
+ return {
441
+ content: [
442
+ {
443
+ type: "text",
444
+ text: `No keys found`,
445
+ },
446
+ ],
447
+ };
448
+ }
449
+
450
+ if (!response.ok) {
451
+ throw new Error(`HTTP error! status: ${response.status}`);
452
+ }
453
+
454
+ const allKeys = await response.json();
455
+
456
+ const searchLower = searchTerm.toLowerCase();
457
+ const matchingKeys = allKeys.filter(key =>
458
+ key.toLowerCase().includes(searchLower)
459
+ );
460
+
461
+ if (matchingKeys.length === 0) {
462
+ return {
463
+ content: [
464
+ {
465
+ type: "text",
466
+ text: `No keys found matching "${searchTerm}"`,
467
+ },
468
+ ],
469
+ };
470
+ }
471
+
472
+ return {
473
+ content: [
474
+ {
475
+ type: "text",
476
+ text: `Found ${matchingKeys.length} key(s) matching "${searchTerm}":\n\n${JSON.stringify(matchingKeys, null, 2)}`,
477
+ },
478
+ ],
479
+ };
480
+ } catch (error) {
481
+ console.error("❌ Error searching keys:", error);
482
+ return {
483
+ content: [
484
+ {
485
+ type: "text",
486
+ text: `Error searching keys: ${error.message}`,
487
+ },
488
+ ],
489
+ };
490
+ }
491
+ }
492
+ );
493
+
494
+ console.error("🚀 Starting QConsul MCP Server");
495
+ console.error(`🚀 Consul URL: ${CONSUL_URL}`);
496
+ console.error(`🔐 Authenticated as: ${CONSUL_USERNAME}`);
497
+ console.error(`🌍 Default Datacenter: ${CONSUL_DATACENTER}`);
498
+
499
+ const transport = new StdioServerTransport();
500
+ await server.connect(transport);