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.
@@ -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=version_data["isVersionOverrideApplied"],
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 new authorization parameters
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)
@@ -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