awslabs.timestream-for-influxdb-mcp-server 0.0.10__tar.gz → 0.0.12__tar.gz

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 (19) hide show
  1. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/Dockerfile +19 -22
  2. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/PKG-INFO +19 -4
  3. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/README.md +18 -3
  4. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/awslabs/timestream_for_influxdb_mcp_server/__init__.py +1 -1
  5. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/awslabs/timestream_for_influxdb_mcp_server/server.py +304 -23
  6. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/docker-healthcheck.sh +1 -1
  7. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/pyproject.toml +1 -1
  8. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/tests/test_server.py +750 -1
  9. awslabs_timestream_for_influxdb_mcp_server-0.0.12/uv-requirements.txt +23 -0
  10. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/uv.lock +11 -10
  11. awslabs_timestream_for_influxdb_mcp_server-0.0.10/uv-requirements.txt +0 -27
  12. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/.gitignore +0 -0
  13. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/.python-version +0 -0
  14. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/CHANGELOG.md +0 -0
  15. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/LICENSE +0 -0
  16. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/NOTICE +0 -0
  17. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/awslabs/__init__.py +0 -0
  18. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/tests/test_init.py +0 -0
  19. {awslabs_timestream_for_influxdb_mcp_server-0.0.10 → awslabs_timestream_for_influxdb_mcp_server-0.0.12}/tests/test_main.py +0 -0
@@ -13,7 +13,11 @@
13
13
  # limitations under the License.
14
14
 
15
15
  # dependabot should continue to update this to the latest hash.
16
- FROM public.ecr.aws/docker/library/python:3.13-alpine@sha256:e7e041128ffc3e3600509f508e44d34ab08ff432bdb62ec508d01dfc5ca459f7 AS uv
16
+ FROM public.ecr.aws/amazonlinux/amazonlinux@sha256:e27a70c006c68f0d194cc9b9624714d6ed8d979a94f60f7d31392f4c8294155b AS uv
17
+
18
+ # Install build dependencies needed for compiling packages
19
+ RUN dnf install -y shadow-utils python3 python3-devel gcc && \
20
+ dnf clean all
17
21
 
18
22
  # Install the project into `/app`
19
23
  WORKDIR /app
@@ -25,7 +29,7 @@ ENV UV_COMPILE_BYTECODE=1
25
29
  ENV UV_LINK_MODE=copy
26
30
 
27
31
  # Prefer the system python
28
- ENV UV_PYTHON_PREFERENCE=only-system
32
+ ENV UV_PYTHON_PREFERENCE=only-managed
29
33
 
30
34
  # Run without updating the uv.lock file like running with `--frozen`
31
35
  ENV UV_FROZEN=true
@@ -37,19 +41,10 @@ COPY pyproject.toml uv.lock uv-requirements.txt ./
37
41
  ENV PIP_NO_CACHE_DIR=1 \
38
42
  PIP_DISABLE_PIP_VERSION_CHECK=1
39
43
 
40
- # Install system dependencies and Python package manager
41
- RUN apk update && \
42
- apk add --no-cache --virtual .build-deps \
43
- build-base \
44
- gcc \
45
- musl-dev \
46
- libffi-dev \
47
- openssl-dev \
48
- cargo
49
-
50
44
  # Install the project's dependencies using the lockfile and settings
51
45
  RUN --mount=type=cache,target=/root/.cache/uv \
52
- pip install --require-hashes --requirement uv-requirements.txt --no-cache-dir && \
46
+ python3 -m ensurepip && \
47
+ python3 -m pip install --require-hashes --requirement uv-requirements.txt --no-cache-dir && \
53
48
  uv sync --python 3.13 --frozen --no-install-project --no-dev --no-editable
54
49
 
55
50
  # Then, add the rest of the project source code and install it
@@ -61,20 +56,22 @@ RUN --mount=type=cache,target=/root/.cache/uv \
61
56
  # Make the directory just in case it doesn't exist
62
57
  RUN mkdir -p /root/.local
63
58
 
64
- FROM public.ecr.aws/docker/library/python:3.13-alpine@sha256:e7e041128ffc3e3600509f508e44d34ab08ff432bdb62ec508d01dfc5ca459f7
59
+ FROM public.ecr.aws/amazonlinux/amazonlinux@sha256:e27a70c006c68f0d194cc9b9624714d6ed8d979a94f60f7d31392f4c8294155b
65
60
 
66
61
  # Place executables in the environment at the front of the path and include other binaries
67
- ENV PATH="/app/.venv/bin:$PATH" \
62
+ ENV PATH="/app/.venv/bin:$PATH:/usr/sbin" \
68
63
  PYTHONUNBUFFERED=1
69
64
 
70
- # Install runtime dependencies and create application user
71
- RUN apk update && \
72
- apk add --no-cache ca-certificates && \
73
- update-ca-certificates && \
74
- addgroup -S app && \
75
- adduser -S app -G app -h /app
65
+ # Install other tools as needed for the MCP server
66
+ # Add non-root user and ability to change directory into /root
67
+ RUN dnf install -y shadow-utils procps && \
68
+ dnf clean all && \
69
+ groupadd --force --system app && \
70
+ useradd app -g app -d /app && \
71
+ chmod o+x /root
76
72
 
77
- # Copy application artifacts from build stage
73
+ # Get the project from the uv layer
74
+ COPY --from=uv --chown=app:app /root/.local /root/.local
78
75
  COPY --from=uv --chown=app:app /app/.venv /app/.venv
79
76
 
80
77
  # Get healthcheck script
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: awslabs.timestream-for-influxdb-mcp-server
3
- Version: 0.0.10
3
+ Version: 0.0.12
4
4
  Summary: An AWS Labs Model Context Protocol (MCP) server for Timestream for InfluxDB
5
5
  Project-URL: homepage, https://awslabs.github.io/mcp/
6
6
  Project-URL: docs, https://awslabs.github.io/mcp/servers/timestream-for-influxdb-mcp-server/
@@ -38,7 +38,8 @@ An AWS Labs Model Context Protocol (MCP) server for Timestream for InfluxDB. Thi
38
38
  - Create, update, list, describe, and delete Timestream for InfluxDB database clusters
39
39
  - Manage DB parameter groups
40
40
  - Tag management for Timestream for InfluxDB resources
41
- - Write and query data using InfluxDB's APIs
41
+ - Manage InfluxDB 2 buckets and organizations
42
+ - Write and query data using InfluxDB 2 APIs
42
43
 
43
44
 
44
45
  ## Pre-requisites
@@ -66,6 +67,9 @@ You can modify the settings of your MCP client to run your local server (e.g. fo
66
67
  "env": {
67
68
  "AWS_PROFILE": "your-aws-profile",
68
69
  "AWS_REGION": "us-east-1",
70
+ "INFLUXDB_URL": "https://your-influxdb-endpoint:8086",
71
+ "INFLUXDB_TOKEN": "your-influxdb-token",
72
+ "INFLUXDB_ORG": "your-influxdb-org",
69
73
  "FASTMCP_LOG_LEVEL": "ERROR"
70
74
  },
71
75
  "disabled": false,
@@ -94,9 +98,12 @@ For Windows users, the MCP server configuration format is slightly different:
94
98
  "awslabs.timestream-for-influxdb-mcp-server.exe"
95
99
  ],
96
100
  "env": {
97
- "FASTMCP_LOG_LEVEL": "ERROR",
98
101
  "AWS_PROFILE": "your-aws-profile",
99
- "AWS_REGION": "us-east-1"
102
+ "AWS_REGION": "us-east-1",
103
+ "INFLUXDB_URL": "https://your-influxdb-endpoint:8086",
104
+ "INFLUXDB_TOKEN": "your-influxdb-token",
105
+ "INFLUXDB_ORG": "your-influxdb-org",
106
+ "FASTMCP_LOG_LEVEL": "ERROR"
100
107
  }
101
108
  }
102
109
  }
@@ -146,3 +153,11 @@ The Timestream for InfluxDB MCP server provides the following tools:
146
153
 
147
154
  ##### Query API
148
155
  - `InfluxDBQuery`: Query data from InfluxDB using Flux query language
156
+
157
+ ##### Bucket Management
158
+ - `InfluxDBListBuckets`: List all buckets in InfluxDB
159
+ - `InfluxDBCreateBucket`: Create a new bucket in InfluxDB
160
+
161
+ ##### Organization Management
162
+ - `InfluxDBListOrgs`: List all organizations in InfluxDB
163
+ - `InfluxDBCreateOrg`: Create a new organization in InfluxDB
@@ -8,7 +8,8 @@ An AWS Labs Model Context Protocol (MCP) server for Timestream for InfluxDB. Thi
8
8
  - Create, update, list, describe, and delete Timestream for InfluxDB database clusters
9
9
  - Manage DB parameter groups
10
10
  - Tag management for Timestream for InfluxDB resources
11
- - Write and query data using InfluxDB's APIs
11
+ - Manage InfluxDB 2 buckets and organizations
12
+ - Write and query data using InfluxDB 2 APIs
12
13
 
13
14
 
14
15
  ## Pre-requisites
@@ -36,6 +37,9 @@ You can modify the settings of your MCP client to run your local server (e.g. fo
36
37
  "env": {
37
38
  "AWS_PROFILE": "your-aws-profile",
38
39
  "AWS_REGION": "us-east-1",
40
+ "INFLUXDB_URL": "https://your-influxdb-endpoint:8086",
41
+ "INFLUXDB_TOKEN": "your-influxdb-token",
42
+ "INFLUXDB_ORG": "your-influxdb-org",
39
43
  "FASTMCP_LOG_LEVEL": "ERROR"
40
44
  },
41
45
  "disabled": false,
@@ -64,9 +68,12 @@ For Windows users, the MCP server configuration format is slightly different:
64
68
  "awslabs.timestream-for-influxdb-mcp-server.exe"
65
69
  ],
66
70
  "env": {
67
- "FASTMCP_LOG_LEVEL": "ERROR",
68
71
  "AWS_PROFILE": "your-aws-profile",
69
- "AWS_REGION": "us-east-1"
72
+ "AWS_REGION": "us-east-1",
73
+ "INFLUXDB_URL": "https://your-influxdb-endpoint:8086",
74
+ "INFLUXDB_TOKEN": "your-influxdb-token",
75
+ "INFLUXDB_ORG": "your-influxdb-org",
76
+ "FASTMCP_LOG_LEVEL": "ERROR"
70
77
  }
71
78
  }
72
79
  }
@@ -116,3 +123,11 @@ The Timestream for InfluxDB MCP server provides the following tools:
116
123
 
117
124
  ##### Query API
118
125
  - `InfluxDBQuery`: Query data from InfluxDB using Flux query language
126
+
127
+ ##### Bucket Management
128
+ - `InfluxDBListBuckets`: List all buckets in InfluxDB
129
+ - `InfluxDBCreateBucket`: Create a new bucket in InfluxDB
130
+
131
+ ##### Organization Management
132
+ - `InfluxDBListOrgs`: List all organizations in InfluxDB
133
+ - `InfluxDBCreateOrg`: Create a new organization in InfluxDB
@@ -15,4 +15,4 @@
15
15
 
16
16
  """awslabs.timestream-for-influxdb-mcp-server"""
17
17
 
18
- __version__ = '0.0.10'
18
+ __version__ = '0.0.12'
@@ -20,6 +20,7 @@ import os
20
20
  from influxdb_client.client.influxdb_client import InfluxDBClient
21
21
  from influxdb_client.client.write.point import Point
22
22
  from influxdb_client.client.write_api import ASYNCHRONOUS, SYNCHRONOUS
23
+ from influxdb_client.domain.bucket_retention_rules import BucketRetentionRules
23
24
  from influxdb_client.domain.write_precision import WritePrecision
24
25
  from loguru import logger
25
26
  from mcp.server.fastmcp import FastMCP
@@ -28,6 +29,11 @@ from typing import Any, Dict, List, Optional
28
29
  from urllib.parse import urlparse
29
30
 
30
31
 
32
+ # InfluxDB environment variables for data plane operations
33
+ INFLUXDB_TOKEN = os.environ.get('INFLUXDB_TOKEN')
34
+ INFLUXDB_URL = os.environ.get('INFLUXDB_URL')
35
+ INFLUXDB_ORG = os.environ.get('INFLUXDB_ORG')
36
+
31
37
  # Define Field parameters as global variables to avoid duplication
32
38
  # Common fields
33
39
  REQUIRED_FIELD_DB_CLUSTER_ID = Field(
@@ -180,10 +186,19 @@ REQUIRED_FIELD_STATUS_CLUSTER = Field(
180
186
  )
181
187
 
182
188
  # InfluxDB fields
183
- REQUIRED_FIELD_URL = Field(..., description='The URL of the InfluxDB server.')
184
- REQUIRED_FIELD_TOKEN = Field(..., description='The authentication token.')
189
+ OPTIONAL_FIELD_URL = Field(
190
+ None,
191
+ description='The URL of the InfluxDB server. Falls back to INFLUXDB_URL env var if not provided.',
192
+ )
193
+ OPTIONAL_FIELD_TOKEN = Field(
194
+ None,
195
+ description='The authentication token. Falls back to INFLUXDB_TOKEN env var if not provided.',
196
+ )
185
197
  REQUIRED_FIELD_BUCKET_INFLUX = Field(..., description='The destination bucket for writes.')
186
- REQUIRED_FIELD_ORG = Field(..., description='The organization name.')
198
+ OPTIONAL_FIELD_ORG = Field(
199
+ None,
200
+ description='The organization name. Falls back to INFLUXDB_ORG env var if not provided.',
201
+ )
187
202
  REQUIRED_FIELD_POINTS = Field(
188
203
  ...,
189
204
  description='List of data points to write. Each point should be a dictionary with measurement, tags, fields, and optional time.',
@@ -242,6 +257,46 @@ def get_timestream_influxdb_client():
242
257
  return client
243
258
 
244
259
 
260
+ def resolve_influxdb_config(
261
+ url: Optional[str] = None,
262
+ token: Optional[str] = None,
263
+ org: Optional[str] = None,
264
+ require_org: bool = True,
265
+ ) -> tuple:
266
+ """Resolve InfluxDB configuration from parameters or environment variables.
267
+
268
+ Args:
269
+ url: The URL of the InfluxDB server (optional, falls back to INFLUXDB_URL env var).
270
+ token: The authentication token (optional, falls back to INFLUXDB_TOKEN env var).
271
+ org: The organization name (optional, falls back to INFLUXDB_ORG env var).
272
+ require_org: Whether the organization is required (default True).
273
+
274
+ Returns:
275
+ A tuple of (resolved_url, resolved_token, resolved_org).
276
+
277
+ Raises:
278
+ ValueError: If required parameters are not provided.
279
+ """
280
+ resolved_url = url or INFLUXDB_URL
281
+ resolved_token = token or INFLUXDB_TOKEN
282
+ resolved_org = org or INFLUXDB_ORG
283
+
284
+ if not resolved_url:
285
+ raise ValueError(
286
+ 'URL must be provided either as parameter or via INFLUXDB_URL environment variable'
287
+ )
288
+ if not resolved_token:
289
+ raise ValueError(
290
+ 'Token must be provided either as parameter or via INFLUXDB_TOKEN environment variable'
291
+ )
292
+ if require_org and not resolved_org:
293
+ raise ValueError(
294
+ 'Organization must be provided either as parameter or via INFLUXDB_ORG environment variable'
295
+ )
296
+
297
+ return resolved_url, resolved_token, resolved_org
298
+
299
+
245
300
  def get_influxdb_client(url, token, org=None, timeout=10000, verify_ssl: bool = True):
246
301
  """Get an InfluxDB client.
247
302
 
@@ -1054,10 +1109,10 @@ async def create_db_parameter_group(
1054
1109
 
1055
1110
  @mcp.tool(name='InfluxDBWritePoints', description='Write data points to InfluxDB endpoint.')
1056
1111
  async def influxdb_write_points(
1057
- url: str = REQUIRED_FIELD_URL,
1058
- token: str = REQUIRED_FIELD_TOKEN,
1059
- bucket: str = REQUIRED_FIELD_BUCKET,
1060
- org: str = REQUIRED_FIELD_ORG,
1112
+ url: Optional[str] = OPTIONAL_FIELD_URL,
1113
+ token: Optional[str] = OPTIONAL_FIELD_TOKEN,
1114
+ bucket: str = REQUIRED_FIELD_BUCKET_INFLUX,
1115
+ org: Optional[str] = OPTIONAL_FIELD_ORG,
1061
1116
  points: List[Dict[str, Any]] = REQUIRED_FIELD_POINTS,
1062
1117
  time_precision: str = OPTIONAL_FIELD_WRITE_PRECISION,
1063
1118
  sync_mode: Optional[str] = OPTIONAL_FIELD_SYNC_MODE,
@@ -1084,10 +1139,12 @@ async def influxdb_write_points(
1084
1139
  'InfluxDBWritePoints tool invocation not allowed when tool-write-mode is set to False'
1085
1140
  )
1086
1141
 
1087
- try:
1088
- client = get_influxdb_client(url, token, org, verify_ssl)
1142
+ resolved_url, resolved_token, resolved_org = resolve_influxdb_config(url, token, org)
1089
1143
 
1090
- # Set write mode
1144
+ try:
1145
+ client = get_influxdb_client(
1146
+ url=resolved_url, token=resolved_token, org=resolved_org, verify_ssl=verify_ssl
1147
+ )
1091
1148
  if sync_mode and sync_mode.lower() == 'synchronous':
1092
1149
  write_api = client.write_api(write_options=SYNCHRONOUS)
1093
1150
  else:
@@ -1117,7 +1174,7 @@ async def influxdb_write_points(
1117
1174
  # Write points
1118
1175
  write_api.write(
1119
1176
  bucket=bucket,
1120
- org=org,
1177
+ org=resolved_org,
1121
1178
  record=influx_points,
1122
1179
  write_precision=getattr(WritePrecision, time_precision.upper()),
1123
1180
  verify_ssl=verify_ssl,
@@ -1137,10 +1194,10 @@ async def influxdb_write_points(
1137
1194
 
1138
1195
  @mcp.tool(name='InfluxDBWriteLP', description='Write data in Line Protocol format to InfluxDB.')
1139
1196
  async def influxdb_write_line_protocol(
1140
- url: str = REQUIRED_FIELD_URL,
1141
- token: str = REQUIRED_FIELD_TOKEN,
1142
- bucket: str = REQUIRED_FIELD_BUCKET,
1143
- org: str = REQUIRED_FIELD_ORG,
1197
+ url: Optional[str] = OPTIONAL_FIELD_URL,
1198
+ token: Optional[str] = OPTIONAL_FIELD_TOKEN,
1199
+ bucket: str = REQUIRED_FIELD_BUCKET_INFLUX,
1200
+ org: Optional[str] = OPTIONAL_FIELD_ORG,
1144
1201
  data_line_protocol: str = REQUIRED_FIELD_DATA_LINE_PROTOCOL,
1145
1202
  time_precision: str = OPTIONAL_FIELD_WRITE_PRECISION,
1146
1203
  sync_mode: str = OPTIONAL_FIELD_SYNC_MODE,
@@ -1157,8 +1214,12 @@ async def influxdb_write_line_protocol(
1157
1214
  'InfluxDBWriteLineProtocol tool invocation not allowed when tool-write-mode is set to False'
1158
1215
  )
1159
1216
 
1217
+ resolved_url, resolved_token, resolved_org = resolve_influxdb_config(url, token, org)
1218
+
1160
1219
  try:
1161
- client = get_influxdb_client(url, token, org)
1220
+ client = get_influxdb_client(
1221
+ url=resolved_url, token=resolved_token, org=resolved_org, verify_ssl=verify_ssl
1222
+ )
1162
1223
 
1163
1224
  # Set write mode
1164
1225
  if sync_mode and sync_mode.lower() == 'synchronous':
@@ -1169,7 +1230,7 @@ async def influxdb_write_line_protocol(
1169
1230
  # Write line protocol
1170
1231
  write_api.write(
1171
1232
  bucket=bucket,
1172
- org=org,
1233
+ org=resolved_org,
1173
1234
  record=data_line_protocol,
1174
1235
  write_precision=getattr(WritePrecision, time_precision.upper()),
1175
1236
  verify_ssl=verify_ssl,
@@ -1189,9 +1250,9 @@ async def influxdb_write_line_protocol(
1189
1250
 
1190
1251
  @mcp.tool(name='InfluxDBQuery', description='Query data from InfluxDB using Flux query language.')
1191
1252
  async def influxdb_query(
1192
- url: str = REQUIRED_FIELD_URL,
1193
- token: str = REQUIRED_FIELD_TOKEN,
1194
- org: str = REQUIRED_FIELD_ORG,
1253
+ url: Optional[str] = OPTIONAL_FIELD_URL,
1254
+ token: Optional[str] = OPTIONAL_FIELD_TOKEN,
1255
+ org: Optional[str] = OPTIONAL_FIELD_ORG,
1195
1256
  query: str = REQUIRED_FIELD_QUERY,
1196
1257
  verify_ssl: bool = OPTIONAL_FIELD_VERIFY_SSL,
1197
1258
  ) -> Dict[str, Any]:
@@ -1200,24 +1261,47 @@ async def influxdb_query(
1200
1261
  Returns:
1201
1262
  Query results in the specified format.
1202
1263
  """
1264
+ resolved_url, resolved_token, resolved_org = resolve_influxdb_config(url, token, org)
1265
+
1203
1266
  try:
1204
- client = get_influxdb_client(url, token, org, verify_ssl)
1267
+ client = get_influxdb_client(
1268
+ url=resolved_url, token=resolved_token, org=resolved_org, verify_ssl=verify_ssl
1269
+ )
1205
1270
  query_api = client.query_api()
1206
1271
 
1207
1272
  # Return as JSON
1208
- tables = query_api.query(org=org, query=query)
1273
+ tables = query_api.query(org=resolved_org, query=query)
1209
1274
 
1210
1275
  # Process the tables into a more usable format
1276
+ # System keys that are not tags
1277
+ system_keys = {
1278
+ 'result',
1279
+ 'table',
1280
+ '_start',
1281
+ '_stop',
1282
+ '_time',
1283
+ '_value',
1284
+ '_field',
1285
+ '_measurement',
1286
+ }
1287
+
1211
1288
  result = []
1212
1289
  for table in tables:
1213
1290
  for record in table.records:
1291
+ # Extract tags by filtering out system keys
1292
+ tags = {
1293
+ k: v
1294
+ for k, v in record.values.items()
1295
+ if k not in system_keys and v is not None
1296
+ }
1297
+
1214
1298
  result.append(
1215
1299
  {
1216
1300
  'measurement': record.get_measurement(),
1217
1301
  'field': record.get_field(),
1218
1302
  'value': record.get_value(),
1219
1303
  'time': record.get_time().isoformat() if record.get_time() else None,
1220
- 'tags': record.values.get('tags', {}),
1304
+ 'tags': tags,
1221
1305
  }
1222
1306
  )
1223
1307
 
@@ -1229,6 +1313,203 @@ async def influxdb_query(
1229
1313
  return {'status': 'error', 'message': str(e)}
1230
1314
 
1231
1315
 
1316
+ @mcp.tool(name='InfluxDBListBuckets', description='List all buckets in InfluxDB.')
1317
+ async def influxdb_list_buckets(
1318
+ url: Optional[str] = OPTIONAL_FIELD_URL,
1319
+ token: Optional[str] = OPTIONAL_FIELD_TOKEN,
1320
+ org: Optional[str] = OPTIONAL_FIELD_ORG,
1321
+ verify_ssl: bool = OPTIONAL_FIELD_VERIFY_SSL,
1322
+ ) -> Dict[str, Any]:
1323
+ """List all buckets in InfluxDB.
1324
+
1325
+ Returns:
1326
+ List of buckets with their details.
1327
+ """
1328
+ resolved_url, resolved_token, resolved_org = resolve_influxdb_config(url, token, org)
1329
+
1330
+ try:
1331
+ client = get_influxdb_client(
1332
+ url=resolved_url, token=resolved_token, org=resolved_org, verify_ssl=verify_ssl
1333
+ )
1334
+ buckets_api = client.buckets_api()
1335
+ buckets = buckets_api.find_buckets().buckets
1336
+
1337
+ result = []
1338
+ for bucket in buckets:
1339
+ result.append(
1340
+ {
1341
+ 'id': bucket.id,
1342
+ 'name': bucket.name,
1343
+ 'org_id': bucket.org_id,
1344
+ 'retention_period': bucket.retention_rules[0].every_seconds
1345
+ if bucket.retention_rules
1346
+ else None,
1347
+ 'created_at': bucket.created_at.isoformat() if bucket.created_at else None,
1348
+ 'updated_at': bucket.updated_at.isoformat() if bucket.updated_at else None,
1349
+ }
1350
+ )
1351
+
1352
+ client.close()
1353
+ return {'status': 'success', 'buckets': result}
1354
+
1355
+ except Exception as e:
1356
+ logger.error(f'Error listing InfluxDB buckets: {str(e)}')
1357
+ return {'status': 'error', 'message': str(e)}
1358
+
1359
+
1360
+ @mcp.tool(name='InfluxDBCreateBucket', description='Create a new bucket in InfluxDB.')
1361
+ async def influxdb_create_bucket(
1362
+ bucket_name: str = Field(..., description='The name of the bucket to create.'),
1363
+ url: Optional[str] = OPTIONAL_FIELD_URL,
1364
+ token: Optional[str] = OPTIONAL_FIELD_TOKEN,
1365
+ org: Optional[str] = OPTIONAL_FIELD_ORG,
1366
+ retention_seconds: Optional[int] = Field(
1367
+ None, description='Retention period in seconds. 0 or None means infinite retention.'
1368
+ ),
1369
+ description: Optional[str] = Field(None, description='Description of the bucket.'),
1370
+ verify_ssl: bool = OPTIONAL_FIELD_VERIFY_SSL,
1371
+ tool_write_mode: bool = OPTIONAL_FIELD_TOOL_WRITE_MODE,
1372
+ ) -> Dict[str, Any]:
1373
+ """Create a new bucket in InfluxDB.
1374
+
1375
+ Returns:
1376
+ Details of the created bucket.
1377
+ """
1378
+ if not tool_write_mode:
1379
+ raise Exception(
1380
+ 'InfluxDBCreateBucket tool invocation not allowed when tool-write-mode is set to False'
1381
+ )
1382
+
1383
+ resolved_url, resolved_token, resolved_org = resolve_influxdb_config(url, token, org)
1384
+
1385
+ try:
1386
+ client = get_influxdb_client(
1387
+ url=resolved_url, token=resolved_token, org=resolved_org, verify_ssl=verify_ssl
1388
+ )
1389
+ buckets_api = client.buckets_api()
1390
+
1391
+ # Get org ID from org name
1392
+ orgs_api = client.organizations_api()
1393
+ orgs = orgs_api.find_organizations(org=resolved_org)
1394
+ if not orgs:
1395
+ raise ValueError(f'Organization "{resolved_org}" not found')
1396
+ org_id = orgs[0].id
1397
+
1398
+ retention_rules = []
1399
+ if retention_seconds and retention_seconds > 0:
1400
+ retention_rules.append(
1401
+ BucketRetentionRules(type='expire', every_seconds=retention_seconds)
1402
+ )
1403
+
1404
+ bucket = buckets_api.create_bucket(
1405
+ bucket_name=bucket_name,
1406
+ org_id=org_id,
1407
+ retention_rules=retention_rules,
1408
+ description=description,
1409
+ )
1410
+
1411
+ client.close()
1412
+ return {
1413
+ 'status': 'success',
1414
+ 'bucket': {
1415
+ 'id': bucket.id,
1416
+ 'name': bucket.name,
1417
+ 'org_id': bucket.org_id,
1418
+ 'retention_period': bucket.retention_rules[0].every_seconds
1419
+ if bucket.retention_rules
1420
+ else None,
1421
+ 'created_at': bucket.created_at.isoformat() if bucket.created_at else None,
1422
+ },
1423
+ }
1424
+
1425
+ except Exception as e:
1426
+ logger.error(f'Error creating InfluxDB bucket: {str(e)}')
1427
+ return {'status': 'error', 'message': str(e)}
1428
+
1429
+
1430
+ @mcp.tool(name='InfluxDBListOrgs', description='List all organizations in InfluxDB.')
1431
+ async def influxdb_list_orgs(
1432
+ url: Optional[str] = OPTIONAL_FIELD_URL,
1433
+ token: Optional[str] = OPTIONAL_FIELD_TOKEN,
1434
+ verify_ssl: bool = OPTIONAL_FIELD_VERIFY_SSL,
1435
+ ) -> Dict[str, Any]:
1436
+ """List all organizations in InfluxDB.
1437
+
1438
+ Returns:
1439
+ List of organizations with their details.
1440
+ """
1441
+ resolved_url, resolved_token, _ = resolve_influxdb_config(
1442
+ url, token, org=None, require_org=False
1443
+ )
1444
+
1445
+ try:
1446
+ client = get_influxdb_client(url=resolved_url, token=resolved_token, verify_ssl=verify_ssl)
1447
+ orgs_api = client.organizations_api()
1448
+ orgs = orgs_api.find_organizations()
1449
+
1450
+ result = []
1451
+ for org in orgs:
1452
+ result.append(
1453
+ {
1454
+ 'id': org.id,
1455
+ 'name': org.name,
1456
+ 'description': org.description,
1457
+ 'created_at': org.created_at.isoformat() if org.created_at else None,
1458
+ 'updated_at': org.updated_at.isoformat() if org.updated_at else None,
1459
+ }
1460
+ )
1461
+
1462
+ client.close()
1463
+ return {'status': 'success', 'organizations': result}
1464
+
1465
+ except Exception as e:
1466
+ logger.error(f'Error listing InfluxDB organizations: {str(e)}')
1467
+ return {'status': 'error', 'message': str(e)}
1468
+
1469
+
1470
+ @mcp.tool(name='InfluxDBCreateOrg', description='Create a new organization in InfluxDB.')
1471
+ async def influxdb_create_org(
1472
+ org_name: str = Field(..., description='The name of the organization to create.'),
1473
+ url: Optional[str] = OPTIONAL_FIELD_URL,
1474
+ token: Optional[str] = OPTIONAL_FIELD_TOKEN,
1475
+ verify_ssl: bool = OPTIONAL_FIELD_VERIFY_SSL,
1476
+ tool_write_mode: bool = OPTIONAL_FIELD_TOOL_WRITE_MODE,
1477
+ ) -> Dict[str, Any]:
1478
+ """Create a new organization in InfluxDB.
1479
+
1480
+ Returns:
1481
+ Details of the created organization.
1482
+ """
1483
+ if not tool_write_mode:
1484
+ raise Exception(
1485
+ 'InfluxDBCreateOrg tool invocation not allowed when tool-write-mode is set to False'
1486
+ )
1487
+
1488
+ resolved_url, resolved_token, _ = resolve_influxdb_config(
1489
+ url, token, org=None, require_org=False
1490
+ )
1491
+
1492
+ try:
1493
+ client = get_influxdb_client(url=resolved_url, token=resolved_token, verify_ssl=verify_ssl)
1494
+ orgs_api = client.organizations_api()
1495
+
1496
+ org = orgs_api.create_organization(name=org_name)
1497
+
1498
+ client.close()
1499
+ return {
1500
+ 'status': 'success',
1501
+ 'organization': {
1502
+ 'id': org.id,
1503
+ 'name': org.name,
1504
+ 'created_at': org.created_at.isoformat() if org.created_at else None,
1505
+ },
1506
+ }
1507
+
1508
+ except Exception as e:
1509
+ logger.error(f'Error creating InfluxDB organization: {str(e)}')
1510
+ return {'status': 'error', 'message': str(e)}
1511
+
1512
+
1232
1513
  def main():
1233
1514
  """Main entry point for the MCP server application."""
1234
1515
  logger.info('Starting Timestream for InfluxDB MCP Server')
@@ -16,7 +16,7 @@
16
16
  SERVER="timestream-for-influxdb-mcp-server"
17
17
 
18
18
  # Check if the server process is running
19
- if pgrep -P 0 -a -l -x -f "/app/.venv/bin/python3 /app/.venv/bin/awslabs.$SERVER" > /dev/null; then
19
+ if pgrep -P 0 -a -l -x -f "/app/.venv/bin/python3? /app/.venv/bin/awslabs.$SERVER" > /dev/null; then
20
20
  echo -n "$SERVER is running";
21
21
  exit 0;
22
22
  fi;
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "awslabs.timestream-for-influxdb-mcp-server"
3
- version = "0.0.10"
3
+ version = "0.0.12"
4
4
  description = "An AWS Labs Model Context Protocol (MCP) server for Timestream for InfluxDB"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.10"