airbyte-internal-ops 0.2.3__py3-none-any.whl → 0.3.0__py3-none-any.whl
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.
- {airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.3.0.dist-info}/METADATA +1 -1
- {airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.3.0.dist-info}/RECORD +13 -9
- airbyte_ops_mcp/cli/cloud.py +79 -0
- airbyte_ops_mcp/cloud_admin/api_client.py +463 -69
- airbyte_ops_mcp/constants.py +3 -0
- airbyte_ops_mcp/gcp_logs/__init__.py +18 -0
- airbyte_ops_mcp/gcp_logs/error_lookup.py +383 -0
- airbyte_ops_mcp/github_api.py +264 -0
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +68 -33
- airbyte_ops_mcp/mcp/gcp_logs.py +92 -0
- airbyte_ops_mcp/mcp/server.py +2 -0
- {airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.3.0.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.3.0.dist-info}/entry_points.txt +0 -0
|
@@ -27,6 +27,12 @@ from airbyte_ops_mcp.cloud_admin.models import (
|
|
|
27
27
|
ConnectorVersionInfo,
|
|
28
28
|
VersionOverrideOperationResult,
|
|
29
29
|
)
|
|
30
|
+
from airbyte_ops_mcp.github_api import (
|
|
31
|
+
GitHubAPIError,
|
|
32
|
+
GitHubCommentParseError,
|
|
33
|
+
GitHubUserEmailNotFoundError,
|
|
34
|
+
get_admin_email_from_approval_comment,
|
|
35
|
+
)
|
|
30
36
|
from airbyte_ops_mcp.mcp._http_headers import (
|
|
31
37
|
resolve_bearer_token,
|
|
32
38
|
resolve_client_id,
|
|
@@ -115,6 +121,7 @@ def get_cloud_connector_version(
|
|
|
115
121
|
|
|
116
122
|
# Use vendored API client instead of connector.get_connector_version()
|
|
117
123
|
# Use Config API root for version management operations
|
|
124
|
+
# Pass workspace_id to get detailed scoped configuration context
|
|
118
125
|
version_data = api_client.get_connector_version(
|
|
119
126
|
connector_id=actor_id,
|
|
120
127
|
connector_type=actor_type,
|
|
@@ -122,13 +129,31 @@ def get_cloud_connector_version(
|
|
|
122
129
|
client_id=auth.client_id,
|
|
123
130
|
client_secret=auth.client_secret,
|
|
124
131
|
bearer_token=auth.bearer_token,
|
|
132
|
+
workspace_id=workspace_id,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
# Determine if version is pinned from scoped config context (more reliable)
|
|
136
|
+
# The API's isVersionOverrideApplied only returns true for USER-created pins,
|
|
137
|
+
# not system-generated pins (e.g., breaking_change origin). Check scopedConfigs
|
|
138
|
+
# for a more accurate picture of whether ANY pin exists.
|
|
139
|
+
scoped_configs = version_data.get("scopedConfigs", {})
|
|
140
|
+
has_any_pin = (
|
|
141
|
+
any(config is not None for config in scoped_configs.values())
|
|
142
|
+
if scoped_configs
|
|
143
|
+
else False
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
# Use scoped config existence as the source of truth for "is pinned"
|
|
147
|
+
# Fall back to API's isVersionOverrideApplied if no scoped config data
|
|
148
|
+
is_pinned = (
|
|
149
|
+
has_any_pin if scoped_configs else version_data["isVersionOverrideApplied"]
|
|
125
150
|
)
|
|
126
151
|
|
|
127
152
|
return ConnectorVersionInfo(
|
|
128
153
|
connector_id=actor_id,
|
|
129
154
|
connector_type=actor_type,
|
|
130
155
|
version=version_data["dockerImageTag"],
|
|
131
|
-
is_version_pinned=
|
|
156
|
+
is_version_pinned=is_pinned,
|
|
132
157
|
)
|
|
133
158
|
except CloudAuthError:
|
|
134
159
|
raise
|
|
@@ -155,6 +180,15 @@ def set_cloud_connector_version_override(
|
|
|
155
180
|
Literal["source", "destination"],
|
|
156
181
|
"The type of connector (source or destination)",
|
|
157
182
|
],
|
|
183
|
+
approval_comment_url: Annotated[
|
|
184
|
+
str,
|
|
185
|
+
Field(
|
|
186
|
+
description="URL to a GitHub comment where the admin has explicitly "
|
|
187
|
+
"requested or authorized this deployment. Must be a valid GitHub comment URL. "
|
|
188
|
+
"Required for authorization. The admin email is automatically derived from "
|
|
189
|
+
"the comment author's GitHub profile.",
|
|
190
|
+
),
|
|
191
|
+
],
|
|
158
192
|
version: Annotated[
|
|
159
193
|
str | None,
|
|
160
194
|
Field(
|
|
@@ -186,14 +220,6 @@ def set_cloud_connector_version_override(
|
|
|
186
220
|
default=None,
|
|
187
221
|
),
|
|
188
222
|
],
|
|
189
|
-
admin_user_email: Annotated[
|
|
190
|
-
str | None,
|
|
191
|
-
Field(
|
|
192
|
-
description="Email of the admin user authorizing this operation. "
|
|
193
|
-
"Must be an @airbyte.io email address. Required for authorization.",
|
|
194
|
-
default=None,
|
|
195
|
-
),
|
|
196
|
-
],
|
|
197
223
|
issue_url: Annotated[
|
|
198
224
|
str | None,
|
|
199
225
|
Field(
|
|
@@ -202,15 +228,6 @@ def set_cloud_connector_version_override(
|
|
|
202
228
|
default=None,
|
|
203
229
|
),
|
|
204
230
|
],
|
|
205
|
-
approval_comment_url: Annotated[
|
|
206
|
-
str | None,
|
|
207
|
-
Field(
|
|
208
|
-
description="URL to a GitHub comment where the admin has explicitly "
|
|
209
|
-
"requested or authorized this deployment. Must be a valid GitHub comment URL. "
|
|
210
|
-
"Required for authorization.",
|
|
211
|
-
default=None,
|
|
212
|
-
),
|
|
213
|
-
],
|
|
214
231
|
ai_agent_session_url: Annotated[
|
|
215
232
|
str | None,
|
|
216
233
|
Field(
|
|
@@ -224,9 +241,13 @@ def set_cloud_connector_version_override(
|
|
|
224
241
|
|
|
225
242
|
**Admin-only operation** - Requires:
|
|
226
243
|
- AIRBYTE_INTERNAL_ADMIN_FLAG=airbyte.io environment variable
|
|
227
|
-
- admin_user_email parameter (must be @airbyte.io email)
|
|
228
244
|
- issue_url parameter (GitHub issue URL for context)
|
|
229
|
-
- approval_comment_url parameter (GitHub comment URL with approval)
|
|
245
|
+
- approval_comment_url parameter (GitHub comment URL with approval from an @airbyte.io user)
|
|
246
|
+
|
|
247
|
+
The admin user email is automatically derived from the approval_comment_url by:
|
|
248
|
+
1. Fetching the comment from GitHub API to get the author's username
|
|
249
|
+
2. Fetching the user's profile to get their public email
|
|
250
|
+
3. Validating the email is an @airbyte.io address
|
|
230
251
|
|
|
231
252
|
You must specify EXACTLY ONE of `version` OR `unset=True`, but not both.
|
|
232
253
|
When setting a version, `override_reason` is required.
|
|
@@ -252,16 +273,9 @@ def set_cloud_connector_version_override(
|
|
|
252
273
|
connector_type=actor_type,
|
|
253
274
|
)
|
|
254
275
|
|
|
255
|
-
# Validate
|
|
276
|
+
# Validate authorization parameters
|
|
256
277
|
validation_errors: list[str] = []
|
|
257
278
|
|
|
258
|
-
if not admin_user_email:
|
|
259
|
-
validation_errors.append("admin_user_email is required for authorization")
|
|
260
|
-
elif "@airbyte.io" not in admin_user_email:
|
|
261
|
-
validation_errors.append(
|
|
262
|
-
f"admin_user_email must be an @airbyte.io email address, got: {admin_user_email}"
|
|
263
|
-
)
|
|
264
|
-
|
|
265
279
|
if not issue_url:
|
|
266
280
|
validation_errors.append(
|
|
267
281
|
"issue_url is required for authorization (GitHub issue URL)"
|
|
@@ -271,11 +285,7 @@ def set_cloud_connector_version_override(
|
|
|
271
285
|
f"issue_url must be a valid GitHub URL (https://github.com/...), got: {issue_url}"
|
|
272
286
|
)
|
|
273
287
|
|
|
274
|
-
if not approval_comment_url:
|
|
275
|
-
validation_errors.append(
|
|
276
|
-
"approval_comment_url is required for authorization (GitHub comment URL)"
|
|
277
|
-
)
|
|
278
|
-
elif not approval_comment_url.startswith("https://github.com/"):
|
|
288
|
+
if not approval_comment_url.startswith("https://github.com/"):
|
|
279
289
|
validation_errors.append(
|
|
280
290
|
f"approval_comment_url must be a valid GitHub URL, got: {approval_comment_url}"
|
|
281
291
|
)
|
|
@@ -296,6 +306,31 @@ def set_cloud_connector_version_override(
|
|
|
296
306
|
connector_type=actor_type,
|
|
297
307
|
)
|
|
298
308
|
|
|
309
|
+
# Derive admin email from approval comment URL
|
|
310
|
+
try:
|
|
311
|
+
admin_user_email = get_admin_email_from_approval_comment(approval_comment_url)
|
|
312
|
+
except GitHubCommentParseError as e:
|
|
313
|
+
return VersionOverrideOperationResult(
|
|
314
|
+
success=False,
|
|
315
|
+
message=f"Failed to parse approval comment URL: {e}",
|
|
316
|
+
connector_id=actor_id,
|
|
317
|
+
connector_type=actor_type,
|
|
318
|
+
)
|
|
319
|
+
except GitHubAPIError as e:
|
|
320
|
+
return VersionOverrideOperationResult(
|
|
321
|
+
success=False,
|
|
322
|
+
message=f"Failed to fetch approval comment from GitHub: {e}",
|
|
323
|
+
connector_id=actor_id,
|
|
324
|
+
connector_type=actor_type,
|
|
325
|
+
)
|
|
326
|
+
except GitHubUserEmailNotFoundError as e:
|
|
327
|
+
return VersionOverrideOperationResult(
|
|
328
|
+
success=False,
|
|
329
|
+
message=str(e),
|
|
330
|
+
connector_id=actor_id,
|
|
331
|
+
connector_type=actor_type,
|
|
332
|
+
)
|
|
333
|
+
|
|
299
334
|
# Build enhanced override reason with audit fields (only for 'set' operations)
|
|
300
335
|
enhanced_override_reason = override_reason
|
|
301
336
|
if not unset and override_reason:
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
+
"""MCP tools for GCP Cloud Logging operations.
|
|
3
|
+
|
|
4
|
+
This module provides MCP tools for querying GCP Cloud Logging,
|
|
5
|
+
particularly for looking up error details by error ID.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Annotated
|
|
11
|
+
|
|
12
|
+
from fastmcp import FastMCP
|
|
13
|
+
from pydantic import Field
|
|
14
|
+
|
|
15
|
+
from airbyte_ops_mcp.gcp_logs import (
|
|
16
|
+
GCPLogSearchResult,
|
|
17
|
+
GCPSeverity,
|
|
18
|
+
fetch_error_logs,
|
|
19
|
+
)
|
|
20
|
+
from airbyte_ops_mcp.gcp_logs.error_lookup import DEFAULT_GCP_PROJECT
|
|
21
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@mcp_tool(
|
|
25
|
+
read_only=True,
|
|
26
|
+
idempotent=True,
|
|
27
|
+
)
|
|
28
|
+
def lookup_cloud_backend_error(
|
|
29
|
+
error_id: Annotated[
|
|
30
|
+
str,
|
|
31
|
+
Field(
|
|
32
|
+
description=(
|
|
33
|
+
"The error ID (UUID) to search for. This is typically returned "
|
|
34
|
+
"in API error responses as {'errorId': '...'}"
|
|
35
|
+
)
|
|
36
|
+
),
|
|
37
|
+
],
|
|
38
|
+
project: Annotated[
|
|
39
|
+
str,
|
|
40
|
+
Field(
|
|
41
|
+
default=DEFAULT_GCP_PROJECT,
|
|
42
|
+
description=(
|
|
43
|
+
"GCP project ID to search in. Defaults to 'prod-ab-cloud-proj' "
|
|
44
|
+
"(Airbyte Cloud production)."
|
|
45
|
+
),
|
|
46
|
+
),
|
|
47
|
+
],
|
|
48
|
+
lookback_days: Annotated[
|
|
49
|
+
int,
|
|
50
|
+
Field(
|
|
51
|
+
default=7,
|
|
52
|
+
description="Number of days to look back in logs. Defaults to 7.",
|
|
53
|
+
),
|
|
54
|
+
],
|
|
55
|
+
min_severity_filter: Annotated[
|
|
56
|
+
GCPSeverity | None,
|
|
57
|
+
Field(
|
|
58
|
+
default=None,
|
|
59
|
+
description="Optional minimum severity level to filter logs.",
|
|
60
|
+
),
|
|
61
|
+
],
|
|
62
|
+
max_log_entries: Annotated[
|
|
63
|
+
int,
|
|
64
|
+
Field(
|
|
65
|
+
default=200,
|
|
66
|
+
description="Maximum number of log entries to return. Defaults to 200.",
|
|
67
|
+
),
|
|
68
|
+
],
|
|
69
|
+
) -> GCPLogSearchResult:
|
|
70
|
+
"""Look up error details from GCP Cloud Logging by error ID.
|
|
71
|
+
|
|
72
|
+
When an Airbyte Cloud API returns an error response with only an error ID
|
|
73
|
+
(e.g., {"errorId": "3173452e-8f22-4286-a1ec-b0f16c1e078a"}), this tool
|
|
74
|
+
fetches the full stack trace and error details from GCP Cloud Logging.
|
|
75
|
+
|
|
76
|
+
The tool searches for log entries containing the error ID and fetches
|
|
77
|
+
related entries (multi-line stack traces) from the same timestamp and pod.
|
|
78
|
+
|
|
79
|
+
Requires GCP credentials with Logs Viewer role on the target project.
|
|
80
|
+
"""
|
|
81
|
+
return fetch_error_logs(
|
|
82
|
+
error_id=error_id,
|
|
83
|
+
project=project,
|
|
84
|
+
lookback_days=lookback_days,
|
|
85
|
+
min_severity_filter=min_severity_filter,
|
|
86
|
+
max_log_entries=max_log_entries,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def register_gcp_logs_tools(app: FastMCP) -> None:
|
|
91
|
+
"""Register GCP logs tools with the FastMCP app."""
|
|
92
|
+
register_mcp_tools(app)
|
airbyte_ops_mcp/mcp/server.py
CHANGED
|
@@ -24,6 +24,7 @@ from airbyte_ops_mcp.constants import MCP_SERVER_NAME
|
|
|
24
24
|
from airbyte_ops_mcp.mcp.cloud_connector_versions import (
|
|
25
25
|
register_cloud_connector_version_tools,
|
|
26
26
|
)
|
|
27
|
+
from airbyte_ops_mcp.mcp.gcp_logs import register_gcp_logs_tools
|
|
27
28
|
from airbyte_ops_mcp.mcp.github import register_github_tools
|
|
28
29
|
from airbyte_ops_mcp.mcp.github_repo_ops import register_github_repo_ops_tools
|
|
29
30
|
from airbyte_ops_mcp.mcp.prerelease import register_prerelease_tools
|
|
@@ -62,6 +63,7 @@ def register_server_assets(app: FastMCP) -> None:
|
|
|
62
63
|
register_prerelease_tools(app)
|
|
63
64
|
register_cloud_connector_version_tools(app)
|
|
64
65
|
register_prod_db_query_tools(app)
|
|
66
|
+
register_gcp_logs_tools(app)
|
|
65
67
|
register_prompts(app)
|
|
66
68
|
register_regression_tests_tools(app)
|
|
67
69
|
|
|
File without changes
|
{airbyte_internal_ops-0.2.3.dist-info → airbyte_internal_ops-0.3.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|