@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,223 @@
1
+ /**
2
+ * MCP Resources Definitions
3
+ *
4
+ * Defines resources that provide read-only data access
5
+ */
6
+ /**
7
+ * Create all MCP resources for InfluxDB data access
8
+ */
9
+ export function createResources(influxService) {
10
+ return [
11
+ {
12
+ name: "influx-config",
13
+ uri: "influx://config",
14
+ description: "InfluxDB configuration and connection status",
15
+ handler: async () => {
16
+ const connectionInfo = influxService.getConnectionInfo();
17
+ const pingResult = await influxService.ping();
18
+ const isConnected = pingResult.ok;
19
+ const config = {
20
+ connection: {
21
+ url: connectionInfo.url,
22
+ hasToken: connectionInfo.hasToken,
23
+ database: connectionInfo.database,
24
+ isConnected,
25
+ },
26
+ };
27
+ return {
28
+ contents: [
29
+ {
30
+ uri: "influx://config",
31
+ text: JSON.stringify(config, null, 2),
32
+ mimeType: "application/json",
33
+ },
34
+ ],
35
+ };
36
+ },
37
+ },
38
+ {
39
+ name: "influx-status",
40
+ uri: "influx://status",
41
+ description: "Comprehensive status of the InfluxDB connection including ping and health check results",
42
+ handler: async () => {
43
+ try {
44
+ const pingResult = await influxService.ping();
45
+ let healthStatus;
46
+ try {
47
+ healthStatus = await influxService.getHealthStatus();
48
+ }
49
+ catch (healthError) {
50
+ healthStatus = {
51
+ status: "unavailable",
52
+ error: healthError.message,
53
+ };
54
+ }
55
+ const status = {
56
+ timestamp: new Date().toISOString(),
57
+ ping: {
58
+ ok: pingResult.ok,
59
+ version: pingResult.version || null,
60
+ build: pingResult.build || null,
61
+ message: pingResult.message || null,
62
+ },
63
+ health: healthStatus,
64
+ overall: {
65
+ status: pingResult.ok && healthStatus.status !== "fail"
66
+ ? "healthy"
67
+ : "unhealthy",
68
+ connection: pingResult.ok ? "connected" : "disconnected",
69
+ },
70
+ };
71
+ return {
72
+ contents: [
73
+ {
74
+ uri: "influx://status",
75
+ text: JSON.stringify(status, null, 2),
76
+ mimeType: "application/json",
77
+ },
78
+ ],
79
+ };
80
+ }
81
+ catch (error) {
82
+ const errorStatus = {
83
+ timestamp: new Date().toISOString(),
84
+ ping: {
85
+ ok: false,
86
+ error: error.message,
87
+ },
88
+ health: {
89
+ status: "unavailable",
90
+ error: "Could not perform health check",
91
+ },
92
+ overall: {
93
+ status: "error",
94
+ connection: "failed",
95
+ },
96
+ error: error.message,
97
+ };
98
+ return {
99
+ contents: [
100
+ {
101
+ uri: "influx://status",
102
+ text: JSON.stringify(errorStatus, null, 2),
103
+ mimeType: "application/json",
104
+ },
105
+ ],
106
+ };
107
+ }
108
+ },
109
+ },
110
+ {
111
+ name: "influx-databases",
112
+ uri: "influx://databases",
113
+ description: "List of all databases in the InfluxDB instance",
114
+ handler: async () => {
115
+ try {
116
+ const databases = await influxService.database.listDatabases();
117
+ const databaseList = {
118
+ timestamp: new Date().toISOString(),
119
+ databases: databases,
120
+ count: databases.length,
121
+ status: "success",
122
+ };
123
+ return {
124
+ contents: [
125
+ {
126
+ uri: "influx://databases",
127
+ text: JSON.stringify(databaseList, null, 2),
128
+ mimeType: "application/json",
129
+ },
130
+ ],
131
+ };
132
+ }
133
+ catch (error) {
134
+ const errorInfo = {
135
+ timestamp: new Date().toISOString(),
136
+ databases: [],
137
+ count: 0,
138
+ status: "error",
139
+ error: error.message,
140
+ connectionInfo: influxService.getConnectionInfo(),
141
+ };
142
+ return {
143
+ contents: [
144
+ {
145
+ uri: "influx://databases",
146
+ text: JSON.stringify(errorInfo, null, 2),
147
+ mimeType: "application/json",
148
+ },
149
+ ],
150
+ };
151
+ }
152
+ },
153
+ },
154
+ {
155
+ name: "context-file",
156
+ uri: "influx://context",
157
+ description: "Custom context file with user-defined database descriptions or documentation",
158
+ handler: async () => {
159
+ try {
160
+ const contextFile = await influxService.contextFile.loadContextFile();
161
+ if (!contextFile) {
162
+ return {
163
+ contents: [
164
+ {
165
+ uri: "influx://context",
166
+ text: JSON.stringify({
167
+ timestamp: new Date().toISOString(),
168
+ status: "not_found",
169
+ message: "No context file found. Create a file in /context/ folder or name a file with 'context' in the name (.json, .txt, .md)",
170
+ searchPaths: [
171
+ "./context/ (any .json, .txt, .md file)",
172
+ "./ (files with 'context' in name)",
173
+ ],
174
+ }, null, 2),
175
+ mimeType: "application/json",
176
+ },
177
+ ],
178
+ };
179
+ }
180
+ let mimeType = "text/plain";
181
+ switch (contextFile.extension) {
182
+ case "json":
183
+ mimeType = "application/json";
184
+ break;
185
+ case "md":
186
+ mimeType = "text/markdown";
187
+ break;
188
+ case "txt":
189
+ default:
190
+ mimeType = "text/plain";
191
+ break;
192
+ }
193
+ return {
194
+ contents: [
195
+ {
196
+ uri: "influx://context",
197
+ text: contextFile.content,
198
+ mimeType: mimeType,
199
+ },
200
+ ],
201
+ };
202
+ }
203
+ catch (error) {
204
+ const errorInfo = {
205
+ timestamp: new Date().toISOString(),
206
+ status: "error",
207
+ error: error.message,
208
+ message: "Failed to load context file",
209
+ };
210
+ return {
211
+ contents: [
212
+ {
213
+ uri: "influx://context",
214
+ text: JSON.stringify(errorInfo, null, 2),
215
+ mimeType: "application/json",
216
+ },
217
+ ],
218
+ };
219
+ }
220
+ },
221
+ },
222
+ ];
223
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * MCP Server Factory
3
+ *
4
+ * Creates and configures the MCP server with all capabilities
5
+ */
6
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
7
+ import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, PingRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
8
+ import { loadConfig, validateConfig } from "../config.js";
9
+ import { InfluxDBMasterService } from "../services/influxdb-master.service.js";
10
+ import { createTools } from "../tools/index.js";
11
+ import { createResources } from "../resources/index.js";
12
+ import { createPrompts } from "../prompts/index.js";
13
+ /**
14
+ * Create and configure the MCP server
15
+ */
16
+ export function createServer() {
17
+ const config = loadConfig();
18
+ validateConfig(config);
19
+ const influxService = new InfluxDBMasterService(config);
20
+ const tools = createTools(influxService);
21
+ const resources = createResources(influxService);
22
+ const prompts = createPrompts(influxService);
23
+ const server = new Server({
24
+ name: config.server.name,
25
+ version: config.server.version,
26
+ }, {
27
+ capabilities: {
28
+ tools: {},
29
+ resources: {},
30
+ prompts: {},
31
+ },
32
+ });
33
+ server.setRequestHandler(ListToolsRequestSchema, async () => {
34
+ return {
35
+ tools: tools.map((tool) => ({
36
+ name: tool.name,
37
+ description: tool.description,
38
+ inputSchema: tool.inputSchema,
39
+ })),
40
+ };
41
+ });
42
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
43
+ const { name, arguments: args } = request.params;
44
+ const tool = tools.find((t) => t.name === name);
45
+ if (!tool) {
46
+ throw new Error(`Unknown tool: ${name}`);
47
+ }
48
+ const validatedArgs = tool.zodSchema.parse(args || {});
49
+ try {
50
+ return await tool.handler(validatedArgs);
51
+ }
52
+ catch (error) {
53
+ return {
54
+ content: [
55
+ {
56
+ type: "text",
57
+ text: `Error: ${error instanceof Error ? error.message : String(error)}`,
58
+ },
59
+ ],
60
+ isError: true,
61
+ };
62
+ }
63
+ });
64
+ server.setRequestHandler(ListResourcesRequestSchema, async () => {
65
+ return {
66
+ resources: resources.map((resource) => ({
67
+ name: resource.name,
68
+ uri: resource.uri,
69
+ description: resource.description,
70
+ mimeType: "application/json",
71
+ })),
72
+ };
73
+ });
74
+ server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
75
+ const { uri } = request.params;
76
+ const resource = resources.find((r) => r.uri === uri);
77
+ if (!resource) {
78
+ throw new Error(`Unknown resource: ${uri}`);
79
+ }
80
+ return await resource.handler();
81
+ });
82
+ server.setRequestHandler(ListPromptsRequestSchema, async () => {
83
+ return {
84
+ prompts: prompts.map((prompt) => ({
85
+ name: prompt.name,
86
+ description: prompt.description,
87
+ arguments: prompt.arguments || [],
88
+ })),
89
+ };
90
+ });
91
+ server.setRequestHandler(GetPromptRequestSchema, async (request) => {
92
+ const { name, arguments: args } = request.params;
93
+ const prompt = prompts.find((p) => p.name === name);
94
+ if (!prompt) {
95
+ throw new Error(`Unknown prompt: ${name}`);
96
+ }
97
+ return await prompt.handler(args);
98
+ });
99
+ server.setRequestHandler(PingRequestSchema, async () => {
100
+ return {};
101
+ });
102
+ console.warn(`[MCP] Server initialized with ${tools.length} tools, ${resources.length} resources, ${prompts.length} prompts`);
103
+ return server;
104
+ }
@@ -0,0 +1,318 @@
1
+ /**
2
+ * Base InfluxDB Connection Service
3
+ *
4
+ * Handles connection management, health checks, and provides base client access
5
+ * for other specialized services
6
+ */
7
+ import { InfluxDBClient } from "@influxdata/influxdb3-client";
8
+ import { HttpClientService } from "./http-client.service.js";
9
+ import { InfluxProductType } from "../helpers/enums/influx-product-types.enum.js";
10
+ export class BaseConnectionService {
11
+ client = null;
12
+ config;
13
+ httpClient;
14
+ constructor(config) {
15
+ this.config = config;
16
+ this.httpClient = new HttpClientService();
17
+ this.initializeClient();
18
+ }
19
+ /**
20
+ * Get the correct host for query/write operations (data plane)
21
+ */
22
+ getDataHost() {
23
+ const influx = this.config.influx;
24
+ if (influx.type === InfluxProductType.CloudDedicated && influx.cluster_id) {
25
+ return `https://${influx.cluster_id}.a.influxdb.io`;
26
+ }
27
+ if (influx.type === InfluxProductType.CloudServerless) {
28
+ return influx.url;
29
+ }
30
+ return influx.url;
31
+ }
32
+ /**
33
+ * Get the correct host for management operations (control plane)
34
+ */
35
+ getManagementHost() {
36
+ const influx = this.config.influx;
37
+ if (influx.type === InfluxProductType.CloudDedicated) {
38
+ return "https://console.influxdata.com";
39
+ }
40
+ if (influx.type === InfluxProductType.CloudServerless) {
41
+ return influx.url;
42
+ }
43
+ return influx.url;
44
+ }
45
+ /**
46
+ * Initialize InfluxDB client
47
+ */
48
+ initializeClient() {
49
+ try {
50
+ const influxConfig = this.config.influx;
51
+ if (this.isValidConfig(influxConfig)) {
52
+ const clientConfig = {
53
+ host: this.getDataHost(),
54
+ token: influxConfig.token,
55
+ };
56
+ this.client = new InfluxDBClient(clientConfig);
57
+ }
58
+ }
59
+ catch (error) {
60
+ console.error("Failed to initialize InfluxDB client:", error);
61
+ this.client = null;
62
+ }
63
+ }
64
+ /**
65
+ * Check if configuration is valid for data operations (requires data client)
66
+ */
67
+ isValidConfig(config) {
68
+ if (config.type === InfluxProductType.CloudDedicated) {
69
+ return !!(config.cluster_id && config.token);
70
+ }
71
+ if (config.type === InfluxProductType.CloudServerless) {
72
+ return !!(config.url && config.token);
73
+ }
74
+ return !!(config.url && config.token);
75
+ }
76
+ /**
77
+ * Check if we have data capabilities (query/write operations)
78
+ */
79
+ hasDataCapabilities() {
80
+ return this.isValidConfig(this.config.influx);
81
+ }
82
+ /**
83
+ * Check if we have management capabilities
84
+ */
85
+ hasManagementCapabilities() {
86
+ const config = this.config.influx;
87
+ if (config.type === InfluxProductType.CloudDedicated) {
88
+ return !!(config.cluster_id &&
89
+ config.account_id &&
90
+ config.management_token);
91
+ }
92
+ if (config.type === InfluxProductType.Clustered) {
93
+ return !!config.management_token;
94
+ }
95
+ if (config.type === InfluxProductType.CloudServerless) {
96
+ return !!(config.url && config.token);
97
+ }
98
+ return !!(config.url && config.token);
99
+ }
100
+ /**
101
+ * Validate that we can perform data operations (query/write)
102
+ * Throws an error if we don't have the necessary configuration
103
+ */
104
+ validateDataCapabilities() {
105
+ if (!this.hasDataCapabilities()) {
106
+ const config = this.config.influx;
107
+ if (config.type === InfluxProductType.CloudDedicated) {
108
+ if (!config.cluster_id) {
109
+ throw new Error("Cloud Dedicated data operations require cluster_id in configuration");
110
+ }
111
+ if (!config.token) {
112
+ throw new Error("Cloud Dedicated data operations require database token in configuration");
113
+ }
114
+ }
115
+ else if (config.type === InfluxProductType.CloudServerless) {
116
+ if (!config.url) {
117
+ throw new Error("Cloud Serverless data operations require url (region-specific endpoint) in configuration");
118
+ }
119
+ if (!config.token) {
120
+ throw new Error("Cloud Serverless data operations require database token in configuration");
121
+ }
122
+ }
123
+ else {
124
+ if (!config.url) {
125
+ throw new Error("Core/Enterprise data operations require url in configuration");
126
+ }
127
+ if (!config.token) {
128
+ throw new Error("Core/Enterprise data operations require token in configuration");
129
+ }
130
+ }
131
+ }
132
+ }
133
+ /**
134
+ * Validate that we can perform management operations
135
+ * Throws an error if we don't have the necessary configuration
136
+ */
137
+ validateManagementCapabilities() {
138
+ if (!this.hasManagementCapabilities()) {
139
+ const config = this.config.influx;
140
+ if (config.type === InfluxProductType.CloudDedicated ||
141
+ config.type === InfluxProductType.Clustered) {
142
+ const missing = [];
143
+ if (!config.cluster_id)
144
+ missing.push("cluster_id");
145
+ if (!config.account_id)
146
+ missing.push("account_id");
147
+ if (!config.management_token)
148
+ missing.push("management_token");
149
+ throw new Error(`Cloud Dedicated/Clustered management operations require: ${missing.join(", ")}`);
150
+ }
151
+ else if (config.type === InfluxProductType.CloudServerless) {
152
+ if (!config.url) {
153
+ throw new Error("Cloud Serverless management operations require url (region-specific endpoint) in configuration");
154
+ }
155
+ if (!config.token) {
156
+ throw new Error("Cloud Serverless management operations require database token in configuration");
157
+ }
158
+ }
159
+ else {
160
+ if (!config.url) {
161
+ throw new Error("Core/Enterprise management operations require url in configuration");
162
+ }
163
+ if (!config.token) {
164
+ throw new Error("Core/Enterprise management operations require token with management permissions");
165
+ }
166
+ }
167
+ }
168
+ }
169
+ /**
170
+ * Validate operation is supported for current product type
171
+ */
172
+ validateOperationSupport(operation, supportedTypes) {
173
+ const currentType = this.config.influx.type;
174
+ if (!supportedTypes.includes(currentType)) {
175
+ const supportedNames = supportedTypes
176
+ .map((type) => {
177
+ switch (type) {
178
+ case InfluxProductType.Core:
179
+ return "Core";
180
+ case InfluxProductType.Enterprise:
181
+ return "Enterprise";
182
+ case InfluxProductType.CloudDedicated:
183
+ return "Cloud Dedicated";
184
+ case InfluxProductType.CloudServerless:
185
+ return "Cloud Serverless";
186
+ case InfluxProductType.Clustered:
187
+ return "Clustered";
188
+ default:
189
+ return type;
190
+ }
191
+ })
192
+ .join(", ");
193
+ const currentName = currentType === InfluxProductType.Core
194
+ ? "Core"
195
+ : currentType === InfluxProductType.Enterprise
196
+ ? "Enterprise"
197
+ : currentType === InfluxProductType.CloudDedicated
198
+ ? "Cloud Dedicated"
199
+ : currentType === InfluxProductType.CloudServerless
200
+ ? "Cloud Serverless"
201
+ : currentType === InfluxProductType.Clustered
202
+ ? "Clustered"
203
+ : currentType;
204
+ throw new Error(`Operation '${operation}' is not supported for ${currentName}. Supported types: ${supportedNames}`);
205
+ }
206
+ }
207
+ /**
208
+ * Get the main client instance
209
+ */
210
+ getClient() {
211
+ return this.client;
212
+ }
213
+ /**
214
+ * Get connection information
215
+ */
216
+ getConnectionInfo() {
217
+ const influxConfig = this.config.influx;
218
+ return {
219
+ isDataClientInitialized: !!this.client,
220
+ url: this.getDataHost() || "",
221
+ hasToken: !!influxConfig.token,
222
+ type: influxConfig.type,
223
+ };
224
+ }
225
+ /**
226
+ * Ping InfluxDB instance (returns version and build info if available)
227
+ */
228
+ async ping() {
229
+ const url = this.getDataHost();
230
+ if (!url) {
231
+ return { ok: false, message: "No data host configured" };
232
+ }
233
+ try {
234
+ const response = await fetch(`${url.replace(/\/$/, "")}/ping`, {
235
+ headers: {
236
+ Authorization: `Token ${this.config.influx.token}`,
237
+ },
238
+ });
239
+ if (response.ok) {
240
+ const version = response.headers.get("x-influxdb-version") || undefined;
241
+ let build = response.headers.get("x-influxdb-build") || undefined;
242
+ if (!build) {
243
+ if (version) {
244
+ build = "Other";
245
+ }
246
+ }
247
+ return { ok: true, version, build };
248
+ }
249
+ else {
250
+ return {
251
+ ok: false,
252
+ message: `Ping failed with status ${response.status}`,
253
+ };
254
+ }
255
+ }
256
+ catch (error) {
257
+ return {
258
+ ok: false,
259
+ message: error instanceof Error ? error.message : String(error),
260
+ };
261
+ }
262
+ }
263
+ /**
264
+ * Get health status
265
+ */
266
+ async getHealthStatus() {
267
+ const url = this.getDataHost();
268
+ if (!url || !this.client) {
269
+ return { status: "fail" };
270
+ }
271
+ try {
272
+ const response = await fetch(`${url.replace(/\/$/, "")}/health`, {
273
+ headers: {
274
+ Authorization: `Token ${this.config.influx.token}`,
275
+ },
276
+ });
277
+ if (response.ok) {
278
+ try {
279
+ const healthData = await response.json();
280
+ return healthData;
281
+ }
282
+ catch {
283
+ return { status: "pass" };
284
+ }
285
+ }
286
+ else {
287
+ return { status: "fail" };
288
+ }
289
+ }
290
+ catch (_error) {
291
+ return { status: "fail" };
292
+ }
293
+ }
294
+ /**
295
+ * Get pre-configured HTTP client for InfluxDB API calls
296
+ * For cloud-dedicated, use data host for query/write, management host for admin
297
+ */
298
+ getInfluxHttpClient(forManagement = false) {
299
+ const influxConfig = this.config.influx;
300
+ const host = (forManagement ? this.getManagementHost() : this.getDataHost()) || "";
301
+ let token = "";
302
+ if (forManagement &&
303
+ (influxConfig.type === InfluxProductType.CloudDedicated ||
304
+ influxConfig.type === InfluxProductType.Clustered)) {
305
+ token = influxConfig.management_token || "";
306
+ }
307
+ else {
308
+ token = influxConfig.token || "";
309
+ }
310
+ return HttpClientService.createInfluxClient(host, token, influxConfig.type);
311
+ }
312
+ /**
313
+ * Get configuration
314
+ */
315
+ getConfig() {
316
+ return this.config;
317
+ }
318
+ }