@influxdata/influxdb3-mcp-server 1.3.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 (40) hide show
  1. package/CHANGELOG.md +194 -0
  2. package/Dockerfile +22 -0
  3. package/LICENSE +6 -0
  4. package/LICENSE-APACHE.txt +201 -0
  5. package/LICENSE-MIT.txt +25 -0
  6. package/README.md +318 -0
  7. package/build/config.js +85 -0
  8. package/build/helpers/enums/influx-product-types.enum.js +8 -0
  9. package/build/index.js +33 -0
  10. package/build/prompts/index.js +98 -0
  11. package/build/resources/index.js +223 -0
  12. package/build/server/index.js +104 -0
  13. package/build/services/base-connection.service.js +318 -0
  14. package/build/services/cloud-token-management.service.js +179 -0
  15. package/build/services/context-file.service.js +97 -0
  16. package/build/services/database-management.service.js +549 -0
  17. package/build/services/help.service.js +241 -0
  18. package/build/services/http-client.service.js +109 -0
  19. package/build/services/influxdb-master.service.js +117 -0
  20. package/build/services/query.service.js +499 -0
  21. package/build/services/serverless-schema-management.service.js +266 -0
  22. package/build/services/token-management.service.js +215 -0
  23. package/build/services/write.service.js +153 -0
  24. package/build/tools/categories/cloud-token.tools.js +321 -0
  25. package/build/tools/categories/database.tools.js +299 -0
  26. package/build/tools/categories/health.tools.js +75 -0
  27. package/build/tools/categories/help.tools.js +104 -0
  28. package/build/tools/categories/query.tools.js +180 -0
  29. package/build/tools/categories/schema.tools.js +308 -0
  30. package/build/tools/categories/token.tools.js +378 -0
  31. package/build/tools/categories/write.tools.js +104 -0
  32. package/build/tools/index.js +27 -0
  33. package/env.example +17 -0
  34. package/example-cloud-dedicated.mcp.json +15 -0
  35. package/example-cloud-serverless.mcp.json +13 -0
  36. package/example-clustered.mcp.json +14 -0
  37. package/example-docker.mcp.json +25 -0
  38. package/example-local.mcp.json +13 -0
  39. package/example-npx.mcp.json +13 -0
  40. package/package.json +78 -0
@@ -0,0 +1,179 @@
1
+ /**
2
+ * InfluxDB Cloud-Dedicated/Clustered Token Management Service
3
+ *
4
+ * Handles database token operations for InfluxDB Cloud-Dedicated and Clustered clusters
5
+ * Uses REST API endpoints instead of SQL queries
6
+ */
7
+ import { InfluxProductType } from "../helpers/enums/influx-product-types.enum.js";
8
+ export class CloudTokenManagementService {
9
+ baseService;
10
+ constructor(baseService) {
11
+ this.baseService = baseService;
12
+ }
13
+ /**
14
+ * List all database tokens for the cloud-dedicated/clustered cluster
15
+ * GET /api/v0/accounts/{accountId}/clusters/{clusterId}/tokens
16
+ */
17
+ async listTokens() {
18
+ this.baseService.validateOperationSupport("list_cloud_tokens", [
19
+ InfluxProductType.CloudDedicated,
20
+ InfluxProductType.Clustered,
21
+ ]);
22
+ this.baseService.validateManagementCapabilities();
23
+ try {
24
+ const httpClient = this.baseService.getInfluxHttpClient(true);
25
+ const config = this.baseService.getConfig();
26
+ const endpoint = `/api/v0/accounts/${config.influx.account_id}/clusters/${config.influx.cluster_id}/tokens`;
27
+ const response = await httpClient.get(endpoint);
28
+ return Array.isArray(response) ? response : [];
29
+ }
30
+ catch (error) {
31
+ this.handleTokenError(error, "list tokens");
32
+ }
33
+ }
34
+ /**
35
+ * Get a specific token by ID
36
+ * GET /api/v0/accounts/{accountId}/clusters/{clusterId}/tokens/{tokenId}
37
+ */
38
+ async getToken(tokenId) {
39
+ this.baseService.validateOperationSupport("get_cloud_token", [
40
+ InfluxProductType.CloudDedicated,
41
+ InfluxProductType.Clustered,
42
+ ]);
43
+ this.baseService.validateManagementCapabilities();
44
+ try {
45
+ const httpClient = this.baseService.getInfluxHttpClient(true);
46
+ const config = this.baseService.getConfig();
47
+ const endpoint = `/api/v0/accounts/${config.influx.account_id}/clusters/${config.influx.cluster_id}/tokens/${tokenId}`;
48
+ const response = await httpClient.get(endpoint);
49
+ return response;
50
+ }
51
+ catch (error) {
52
+ this.handleTokenError(error, `get token '${tokenId}'`);
53
+ }
54
+ }
55
+ /**
56
+ * Create a new database token
57
+ * POST /api/v0/accounts/{accountId}/clusters/{clusterId}/tokens
58
+ */
59
+ async createToken(request) {
60
+ this.baseService.validateOperationSupport("create_cloud_token", [
61
+ InfluxProductType.CloudDedicated,
62
+ InfluxProductType.Clustered,
63
+ ]);
64
+ this.baseService.validateManagementCapabilities();
65
+ try {
66
+ const httpClient = this.baseService.getInfluxHttpClient(true);
67
+ const config = this.baseService.getConfig();
68
+ const endpoint = `/api/v0/accounts/${config.influx.account_id}/clusters/${config.influx.cluster_id}/tokens`;
69
+ const response = await httpClient.post(endpoint, request);
70
+ return response;
71
+ }
72
+ catch (error) {
73
+ this.handleTokenError(error, `create token '${request.description}'`);
74
+ }
75
+ }
76
+ /**
77
+ * Update an existing token
78
+ * PATCH /api/v0/accounts/{accountId}/clusters/{clusterId}/tokens/{tokenId}
79
+ */
80
+ async updateToken(tokenId, request) {
81
+ this.baseService.validateOperationSupport("update_cloud_token", [
82
+ InfluxProductType.CloudDedicated,
83
+ InfluxProductType.Clustered,
84
+ ]);
85
+ this.baseService.validateManagementCapabilities();
86
+ try {
87
+ const httpClient = this.baseService.getInfluxHttpClient(true);
88
+ const config = this.baseService.getConfig();
89
+ const endpoint = `/api/v0/accounts/${config.influx.account_id}/clusters/${config.influx.cluster_id}/tokens/${tokenId}`;
90
+ const response = await httpClient.patch(endpoint, request);
91
+ return response;
92
+ }
93
+ catch (error) {
94
+ this.handleTokenError(error, `update token '${tokenId}'`);
95
+ }
96
+ }
97
+ /**
98
+ * Delete a token
99
+ * DELETE /api/v0/accounts/{accountId}/clusters/{clusterId}/tokens/{tokenId}
100
+ */
101
+ async deleteToken(tokenId) {
102
+ this.baseService.validateOperationSupport("delete_cloud_token", [
103
+ InfluxProductType.CloudDedicated,
104
+ InfluxProductType.Clustered,
105
+ ]);
106
+ this.baseService.validateManagementCapabilities();
107
+ try {
108
+ const httpClient = this.baseService.getInfluxHttpClient(true);
109
+ const config = this.baseService.getConfig();
110
+ const endpoint = `/api/v0/accounts/${config.influx.account_id}/clusters/${config.influx.cluster_id}/tokens/${tokenId}`;
111
+ await httpClient.delete(endpoint);
112
+ return true;
113
+ }
114
+ catch (error) {
115
+ this.handleTokenError(error, `delete token '${tokenId}'`);
116
+ }
117
+ }
118
+ /**
119
+ * Helper method to create permissions for specific databases and actions
120
+ */
121
+ createPermissions(databases, actions) {
122
+ const permissions = [];
123
+ for (const database of databases) {
124
+ for (const action of actions) {
125
+ permissions.push({
126
+ action,
127
+ resource: database,
128
+ });
129
+ }
130
+ }
131
+ return permissions;
132
+ }
133
+ /**
134
+ * Common error handling for token operations
135
+ */
136
+ handleTokenError(error, operation) {
137
+ const status = error.response?.status;
138
+ const originalMessage = error.response?.data?.message ||
139
+ error.response?.data?.error ||
140
+ (typeof error.response?.data === "string" ? error.response.data : null) ||
141
+ error.response?.statusText;
142
+ const statusText = error.response?.statusText || "";
143
+ const formatError = (userMessage) => {
144
+ const parts = [`HTTP ${status}`, userMessage];
145
+ if (originalMessage && originalMessage !== statusText) {
146
+ parts.push(`Server message: ${originalMessage}`);
147
+ }
148
+ return parts.join(" - ");
149
+ };
150
+ switch (status) {
151
+ case 400:
152
+ throw new Error(formatError("Bad Request: Invalid request parameters or malformed request"));
153
+ case 401:
154
+ throw new Error(formatError("Unauthorized: Check your InfluxDB management token permissions"));
155
+ case 403:
156
+ throw new Error(formatError("Forbidden: Management token does not have sufficient permissions"));
157
+ case 404:
158
+ throw new Error(formatError("Not Found: Token does not exist or endpoint not available"));
159
+ case 409:
160
+ throw new Error(formatError("Conflict: Token already exists or operation conflicts with current state"));
161
+ case 500:
162
+ throw new Error(formatError("Internal Server Error: InfluxDB server encountered an error"));
163
+ default:
164
+ if (error.code === "ECONNREFUSED") {
165
+ throw new Error("Connection refused: Check if InfluxDB Cloud console is accessible");
166
+ }
167
+ else if (error.code === "ENOTFOUND") {
168
+ throw new Error("Host not found: Check your InfluxDB Cloud console URL");
169
+ }
170
+ else if (error.response?.data) {
171
+ const message = originalMessage || JSON.stringify(error.response.data);
172
+ throw new Error(`HTTP ${status} - InfluxDB Cloud API error: ${message}`);
173
+ }
174
+ else {
175
+ throw new Error(`Failed to ${operation}: ${error.message}`);
176
+ }
177
+ }
178
+ }
179
+ }
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Context File Service
3
+ *
4
+ * Searches for and loads custom context files to enhance the MCP server
5
+ * with user-specific database descriptions or documentation.
6
+ */
7
+ import { readFileSync, existsSync, readdirSync } from "fs";
8
+ import { join, extname, basename } from "path";
9
+ export class ContextFileService {
10
+ workingDirectory;
11
+ constructor(workingDirectory = process.cwd()) {
12
+ this.workingDirectory = workingDirectory;
13
+ }
14
+ /**
15
+ * Search for and load context file
16
+ * Priority:
17
+ * 1. /context/ folder (any file)
18
+ * 2. Files with "context" in name with .json, .txt, .md extensions
19
+ */
20
+ async loadContextFile() {
21
+ try {
22
+ const contextFolderPath = join(this.workingDirectory, "context");
23
+ const contextFolderFile = this.searchInContextFolder(contextFolderPath);
24
+ if (contextFolderFile) {
25
+ return contextFolderFile;
26
+ }
27
+ const contextNamedFile = this.searchForContextNamedFiles();
28
+ return contextNamedFile;
29
+ }
30
+ catch (error) {
31
+ console.error("Error loading context file:", error.message);
32
+ return null;
33
+ }
34
+ }
35
+ /**
36
+ * Search for files in /context/ folder
37
+ */
38
+ searchInContextFolder(contextFolderPath) {
39
+ if (!existsSync(contextFolderPath)) {
40
+ return null;
41
+ }
42
+ try {
43
+ const files = readdirSync(contextFolderPath);
44
+ const validExtensions = [".json", ".txt", ".md"];
45
+ for (const file of files) {
46
+ const filePath = join(contextFolderPath, file);
47
+ const ext = extname(file).toLowerCase();
48
+ if (validExtensions.includes(ext)) {
49
+ const content = readFileSync(filePath, "utf-8");
50
+ return {
51
+ path: filePath,
52
+ name: basename(file, ext),
53
+ extension: ext.substring(1),
54
+ content,
55
+ exists: true,
56
+ };
57
+ }
58
+ }
59
+ }
60
+ catch (error) {
61
+ console.error("Error reading context folder:", error.message);
62
+ }
63
+ return null;
64
+ }
65
+ /**
66
+ * Search for files with "context" in name
67
+ */
68
+ searchForContextNamedFiles() {
69
+ try {
70
+ const files = readdirSync(this.workingDirectory);
71
+ const validExtensions = [".json", ".txt", ".md"];
72
+ const contextFiles = files.filter((file) => {
73
+ const ext = extname(file).toLowerCase();
74
+ const nameWithoutExt = basename(file, ext).toLowerCase();
75
+ return (validExtensions.includes(ext) && nameWithoutExt.includes("context"));
76
+ });
77
+ if (contextFiles.length === 0) {
78
+ return null;
79
+ }
80
+ const file = contextFiles[0];
81
+ const filePath = join(this.workingDirectory, file);
82
+ const ext = extname(file).toLowerCase();
83
+ const content = readFileSync(filePath, "utf-8");
84
+ return {
85
+ path: filePath,
86
+ name: basename(file, ext),
87
+ extension: ext.substring(1),
88
+ content,
89
+ exists: true,
90
+ };
91
+ }
92
+ catch (error) {
93
+ console.error("Error searching for context files:", error.message);
94
+ return null;
95
+ }
96
+ }
97
+ }