airbyte-internal-ops 0.1.11__py3-none-any.whl → 0.2.1__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.1.11.dist-info → airbyte_internal_ops-0.2.1.dist-info}/METADATA +2 -2
- {airbyte_internal_ops-0.1.11.dist-info → airbyte_internal_ops-0.2.1.dist-info}/RECORD +41 -40
- {airbyte_internal_ops-0.1.11.dist-info → airbyte_internal_ops-0.2.1.dist-info}/entry_points.txt +1 -0
- airbyte_ops_mcp/__init__.py +2 -2
- airbyte_ops_mcp/cli/cloud.py +264 -301
- airbyte_ops_mcp/cloud_admin/api_client.py +51 -26
- airbyte_ops_mcp/cloud_admin/auth.py +32 -0
- airbyte_ops_mcp/cloud_admin/connection_config.py +2 -2
- airbyte_ops_mcp/constants.py +18 -0
- airbyte_ops_mcp/github_actions.py +94 -5
- airbyte_ops_mcp/mcp/_http_headers.py +254 -0
- airbyte_ops_mcp/mcp/_mcp_utils.py +2 -2
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +162 -52
- airbyte_ops_mcp/mcp/github.py +34 -1
- airbyte_ops_mcp/mcp/prod_db_queries.py +67 -24
- airbyte_ops_mcp/mcp/{live_tests.py → regression_tests.py} +165 -152
- airbyte_ops_mcp/mcp/server.py +84 -11
- airbyte_ops_mcp/prod_db_access/db_engine.py +15 -11
- airbyte_ops_mcp/prod_db_access/queries.py +27 -15
- airbyte_ops_mcp/prod_db_access/sql.py +17 -16
- airbyte_ops_mcp/{live_tests → regression_tests}/__init__.py +3 -3
- airbyte_ops_mcp/{live_tests → regression_tests}/cdk_secrets.py +1 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/connection_secret_retriever.py +3 -3
- airbyte_ops_mcp/{live_tests → regression_tests}/connector_runner.py +1 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/message_cache/__init__.py +3 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/regression/__init__.py +1 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/schema_generation.py +3 -1
- airbyte_ops_mcp/{live_tests → regression_tests}/validation/__init__.py +2 -2
- airbyte_ops_mcp/{live_tests → regression_tests}/validation/record_validators.py +4 -2
- {airbyte_internal_ops-0.1.11.dist-info → airbyte_internal_ops-0.2.1.dist-info}/WHEEL +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/ci_output.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/commons/__init__.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/config.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/connection_fetcher.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/evaluation_modes.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/http_metrics.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/message_cache/duckdb_cache.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/models.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/obfuscation.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/regression/comparators.py +0 -0
- /airbyte_ops_mcp/{live_tests → regression_tests}/validation/catalog_validators.py +0 -0
|
@@ -5,20 +5,15 @@ This module provides MCP tools for viewing and managing connector version
|
|
|
5
5
|
overrides (pins) in Airbyte Cloud. These tools enable admins to pin connectors
|
|
6
6
|
to specific versions for troubleshooting or stability purposes.
|
|
7
7
|
|
|
8
|
-
Uses
|
|
9
|
-
for all cloud operations.
|
|
8
|
+
Uses direct API client calls with either bearer token or client credentials auth.
|
|
10
9
|
"""
|
|
11
10
|
|
|
12
11
|
from __future__ import annotations
|
|
13
12
|
|
|
13
|
+
from dataclasses import dataclass
|
|
14
14
|
from typing import Annotated, Literal
|
|
15
15
|
|
|
16
16
|
from airbyte import constants
|
|
17
|
-
from airbyte.cloud import CloudWorkspace
|
|
18
|
-
from airbyte.cloud.auth import (
|
|
19
|
-
resolve_cloud_client_id,
|
|
20
|
-
resolve_cloud_client_secret,
|
|
21
|
-
)
|
|
22
17
|
from airbyte.exceptions import PyAirbyteInputError
|
|
23
18
|
from fastmcp import FastMCP
|
|
24
19
|
from pydantic import Field
|
|
@@ -26,39 +21,64 @@ from pydantic import Field
|
|
|
26
21
|
from airbyte_ops_mcp.cloud_admin import api_client
|
|
27
22
|
from airbyte_ops_mcp.cloud_admin.auth import (
|
|
28
23
|
CloudAuthError,
|
|
29
|
-
|
|
30
|
-
require_internal_admin,
|
|
24
|
+
require_internal_admin_flag_only,
|
|
31
25
|
)
|
|
32
26
|
from airbyte_ops_mcp.cloud_admin.models import (
|
|
33
27
|
ConnectorVersionInfo,
|
|
34
28
|
VersionOverrideOperationResult,
|
|
35
29
|
)
|
|
30
|
+
from airbyte_ops_mcp.mcp._http_headers import (
|
|
31
|
+
resolve_bearer_token,
|
|
32
|
+
resolve_client_id,
|
|
33
|
+
resolve_client_secret,
|
|
34
|
+
)
|
|
36
35
|
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
37
36
|
|
|
38
37
|
|
|
39
|
-
|
|
40
|
-
|
|
38
|
+
@dataclass(frozen=True)
|
|
39
|
+
class _ResolvedCloudAuth:
|
|
40
|
+
"""Resolved authentication for Airbyte Cloud API calls.
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
Either bearer_token OR (client_id AND client_secret) will be set, not both.
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
bearer_token: str | None = None
|
|
46
|
+
client_id: str | None = None
|
|
47
|
+
client_secret: str | None = None
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _resolve_cloud_auth() -> _ResolvedCloudAuth:
|
|
51
|
+
"""Resolve authentication credentials for Airbyte Cloud API.
|
|
52
|
+
|
|
53
|
+
Credentials are resolved in priority order:
|
|
54
|
+
1. Bearer token (Authorization header or AIRBYTE_CLOUD_BEARER_TOKEN env var)
|
|
55
|
+
2. Client credentials (X-Airbyte-Cloud-Client-Id/Secret headers or env vars)
|
|
44
56
|
|
|
45
57
|
Returns:
|
|
46
|
-
|
|
58
|
+
_ResolvedCloudAuth with either bearer_token or client credentials set.
|
|
47
59
|
|
|
48
60
|
Raises:
|
|
49
|
-
CloudAuthError: If
|
|
61
|
+
CloudAuthError: If credentials cannot be resolved from headers or env vars.
|
|
50
62
|
"""
|
|
63
|
+
# Try bearer token first (preferred)
|
|
64
|
+
bearer_token = resolve_bearer_token()
|
|
65
|
+
if bearer_token:
|
|
66
|
+
return _ResolvedCloudAuth(bearer_token=str(bearer_token))
|
|
67
|
+
|
|
68
|
+
# Fall back to client credentials
|
|
51
69
|
try:
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
70
|
+
client_id = resolve_client_id()
|
|
71
|
+
client_secret = resolve_client_secret()
|
|
72
|
+
return _ResolvedCloudAuth(
|
|
73
|
+
client_id=str(client_id),
|
|
74
|
+
client_secret=str(client_secret),
|
|
57
75
|
)
|
|
58
76
|
except Exception as e:
|
|
59
77
|
raise CloudAuthError(
|
|
60
|
-
f"Failed to
|
|
61
|
-
f"
|
|
78
|
+
f"Failed to resolve credentials. Ensure credentials are provided "
|
|
79
|
+
f"via Authorization header (Bearer token), "
|
|
80
|
+
f"HTTP headers (X-Airbyte-Cloud-Client-Id, X-Airbyte-Cloud-Client-Secret), "
|
|
81
|
+
f"or environment variables. Error: {e}"
|
|
62
82
|
) from e
|
|
63
83
|
|
|
64
84
|
|
|
@@ -85,11 +105,13 @@ def get_cloud_connector_version(
|
|
|
85
105
|
Returns version details including the current version string and whether
|
|
86
106
|
an override (pin) is applied.
|
|
87
107
|
|
|
88
|
-
|
|
89
|
-
|
|
108
|
+
Authentication credentials are resolved in priority order:
|
|
109
|
+
1. Bearer token (Authorization header or AIRBYTE_CLOUD_BEARER_TOKEN env var)
|
|
110
|
+
2. HTTP headers: X-Airbyte-Cloud-Client-Id, X-Airbyte-Cloud-Client-Secret
|
|
111
|
+
3. Environment variables: AIRBYTE_CLOUD_CLIENT_ID, AIRBYTE_CLOUD_CLIENT_SECRET
|
|
90
112
|
"""
|
|
91
113
|
try:
|
|
92
|
-
|
|
114
|
+
auth = _resolve_cloud_auth()
|
|
93
115
|
|
|
94
116
|
# Use vendored API client instead of connector.get_connector_version()
|
|
95
117
|
# Use Config API root for version management operations
|
|
@@ -97,8 +119,9 @@ def get_cloud_connector_version(
|
|
|
97
119
|
connector_id=actor_id,
|
|
98
120
|
connector_type=actor_type,
|
|
99
121
|
api_root=constants.CLOUD_CONFIG_API_ROOT, # Use Config API, not public API
|
|
100
|
-
client_id=
|
|
101
|
-
client_secret=
|
|
122
|
+
client_id=auth.client_id,
|
|
123
|
+
client_secret=auth.client_secret,
|
|
124
|
+
bearer_token=auth.bearer_token,
|
|
102
125
|
)
|
|
103
126
|
|
|
104
127
|
return ConnectorVersionInfo(
|
|
@@ -163,11 +186,47 @@ def set_cloud_connector_version_override(
|
|
|
163
186
|
default=None,
|
|
164
187
|
),
|
|
165
188
|
],
|
|
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
|
+
issue_url: Annotated[
|
|
198
|
+
str | None,
|
|
199
|
+
Field(
|
|
200
|
+
description="URL to the GitHub issue providing context for this operation. "
|
|
201
|
+
"Must be a valid GitHub URL (https://github.com/...). Required for authorization.",
|
|
202
|
+
default=None,
|
|
203
|
+
),
|
|
204
|
+
],
|
|
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
|
+
ai_agent_session_url: Annotated[
|
|
215
|
+
str | None,
|
|
216
|
+
Field(
|
|
217
|
+
description="URL to the AI agent session driving this operation, if applicable. "
|
|
218
|
+
"Provides additional auditability for AI-driven operations.",
|
|
219
|
+
default=None,
|
|
220
|
+
),
|
|
221
|
+
],
|
|
166
222
|
) -> VersionOverrideOperationResult:
|
|
167
223
|
"""Set or clear a version override for a deployed connector.
|
|
168
224
|
|
|
169
|
-
**Admin-only operation** - Requires
|
|
170
|
-
|
|
225
|
+
**Admin-only operation** - Requires:
|
|
226
|
+
- AIRBYTE_INTERNAL_ADMIN_FLAG=airbyte.io environment variable
|
|
227
|
+
- admin_user_email parameter (must be @airbyte.io email)
|
|
228
|
+
- issue_url parameter (GitHub issue URL for context)
|
|
229
|
+
- approval_comment_url parameter (GitHub comment URL with approval)
|
|
171
230
|
|
|
172
231
|
You must specify EXACTLY ONE of `version` OR `unset=True`, but not both.
|
|
173
232
|
When setting a version, `override_reason` is required.
|
|
@@ -177,13 +236,14 @@ def set_cloud_connector_version_override(
|
|
|
177
236
|
- Production versions: Require strong justification mentioning customer/support/investigation
|
|
178
237
|
- Release candidates (-rc): Any admin can pin/unpin RC versions
|
|
179
238
|
|
|
180
|
-
|
|
181
|
-
|
|
239
|
+
Authentication credentials are resolved in priority order:
|
|
240
|
+
1. Bearer token (Authorization header or AIRBYTE_CLOUD_BEARER_TOKEN env var)
|
|
241
|
+
2. HTTP headers: X-Airbyte-Cloud-Client-Id, X-Airbyte-Cloud-Client-Secret
|
|
242
|
+
3. Environment variables: AIRBYTE_CLOUD_CLIENT_ID, AIRBYTE_CLOUD_CLIENT_SECRET
|
|
182
243
|
"""
|
|
183
|
-
# Validate admin access
|
|
244
|
+
# Validate admin access (check env var flag)
|
|
184
245
|
try:
|
|
185
|
-
|
|
186
|
-
user_email = get_admin_user_email()
|
|
246
|
+
require_internal_admin_flag_only()
|
|
187
247
|
except CloudAuthError as e:
|
|
188
248
|
return VersionOverrideOperationResult(
|
|
189
249
|
success=False,
|
|
@@ -192,17 +252,72 @@ def set_cloud_connector_version_override(
|
|
|
192
252
|
connector_type=actor_type,
|
|
193
253
|
)
|
|
194
254
|
|
|
195
|
-
#
|
|
255
|
+
# Validate new authorization parameters
|
|
256
|
+
validation_errors: list[str] = []
|
|
257
|
+
|
|
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
|
+
if not issue_url:
|
|
266
|
+
validation_errors.append(
|
|
267
|
+
"issue_url is required for authorization (GitHub issue URL)"
|
|
268
|
+
)
|
|
269
|
+
elif not issue_url.startswith("https://github.com/"):
|
|
270
|
+
validation_errors.append(
|
|
271
|
+
f"issue_url must be a valid GitHub URL (https://github.com/...), got: {issue_url}"
|
|
272
|
+
)
|
|
273
|
+
|
|
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/"):
|
|
279
|
+
validation_errors.append(
|
|
280
|
+
f"approval_comment_url must be a valid GitHub URL, got: {approval_comment_url}"
|
|
281
|
+
)
|
|
282
|
+
elif (
|
|
283
|
+
"#issuecomment-" not in approval_comment_url
|
|
284
|
+
and "#discussion_r" not in approval_comment_url
|
|
285
|
+
):
|
|
286
|
+
validation_errors.append(
|
|
287
|
+
"approval_comment_url must be a GitHub comment URL "
|
|
288
|
+
"(containing #issuecomment- or #discussion_r)"
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
if validation_errors:
|
|
292
|
+
return VersionOverrideOperationResult(
|
|
293
|
+
success=False,
|
|
294
|
+
message="Authorization validation failed: " + "; ".join(validation_errors),
|
|
295
|
+
connector_id=actor_id,
|
|
296
|
+
connector_type=actor_type,
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
# Build enhanced override reason with audit fields (only for 'set' operations)
|
|
300
|
+
enhanced_override_reason = override_reason
|
|
301
|
+
if not unset and override_reason:
|
|
302
|
+
audit_parts = [override_reason]
|
|
303
|
+
audit_parts.append(f"Issue: {issue_url}")
|
|
304
|
+
audit_parts.append(f"Approval: {approval_comment_url}")
|
|
305
|
+
if ai_agent_session_url:
|
|
306
|
+
audit_parts.append(f"AI Session: {ai_agent_session_url}")
|
|
307
|
+
enhanced_override_reason = " | ".join(audit_parts)
|
|
308
|
+
|
|
309
|
+
# Resolve auth and get current version info
|
|
196
310
|
try:
|
|
197
|
-
|
|
311
|
+
auth = _resolve_cloud_auth()
|
|
198
312
|
|
|
199
313
|
# Get current version info before the operation
|
|
200
314
|
current_version_data = api_client.get_connector_version(
|
|
201
315
|
connector_id=actor_id,
|
|
202
316
|
connector_type=actor_type,
|
|
203
317
|
api_root=constants.CLOUD_CONFIG_API_ROOT, # Use Config API
|
|
204
|
-
client_id=
|
|
205
|
-
client_secret=
|
|
318
|
+
client_id=auth.client_id,
|
|
319
|
+
client_secret=auth.client_secret,
|
|
320
|
+
bearer_token=auth.bearer_token,
|
|
206
321
|
)
|
|
207
322
|
current_version = current_version_data["dockerImageTag"]
|
|
208
323
|
was_pinned_before = current_version_data["isVersionOverrideApplied"]
|
|
@@ -210,14 +325,7 @@ def set_cloud_connector_version_override(
|
|
|
210
325
|
except CloudAuthError as e:
|
|
211
326
|
return VersionOverrideOperationResult(
|
|
212
327
|
success=False,
|
|
213
|
-
message=f"Failed to
|
|
214
|
-
connector_id=actor_id,
|
|
215
|
-
connector_type=actor_type,
|
|
216
|
-
)
|
|
217
|
-
except Exception as e:
|
|
218
|
-
return VersionOverrideOperationResult(
|
|
219
|
-
success=False,
|
|
220
|
-
message=f"Failed to get connector: {e}",
|
|
328
|
+
message=f"Failed to resolve credentials or get connector: {e}",
|
|
221
329
|
connector_id=actor_id,
|
|
222
330
|
connector_type=actor_type,
|
|
223
331
|
)
|
|
@@ -228,14 +336,15 @@ def set_cloud_connector_version_override(
|
|
|
228
336
|
connector_id=actor_id,
|
|
229
337
|
connector_type=actor_type,
|
|
230
338
|
api_root=constants.CLOUD_CONFIG_API_ROOT, # Use Config API
|
|
231
|
-
client_id=
|
|
232
|
-
client_secret=
|
|
339
|
+
client_id=auth.client_id,
|
|
340
|
+
client_secret=auth.client_secret,
|
|
233
341
|
workspace_id=workspace_id,
|
|
234
342
|
version=version,
|
|
235
343
|
unset=unset,
|
|
236
|
-
override_reason=
|
|
344
|
+
override_reason=enhanced_override_reason,
|
|
237
345
|
override_reason_reference_url=override_reason_reference_url,
|
|
238
|
-
user_email=
|
|
346
|
+
user_email=admin_user_email,
|
|
347
|
+
bearer_token=auth.bearer_token,
|
|
239
348
|
)
|
|
240
349
|
|
|
241
350
|
# Get updated version info after the operation
|
|
@@ -243,8 +352,9 @@ def set_cloud_connector_version_override(
|
|
|
243
352
|
connector_id=actor_id,
|
|
244
353
|
connector_type=actor_type,
|
|
245
354
|
api_root=constants.CLOUD_CONFIG_API_ROOT, # Use Config API
|
|
246
|
-
client_id=
|
|
247
|
-
client_secret=
|
|
355
|
+
client_id=auth.client_id,
|
|
356
|
+
client_secret=auth.client_secret,
|
|
357
|
+
bearer_token=auth.bearer_token,
|
|
248
358
|
)
|
|
249
359
|
new_version = updated_version_data["dockerImageTag"] if not unset else None
|
|
250
360
|
is_pinned_after = updated_version_data["isVersionOverrideApplied"]
|
airbyte_ops_mcp/mcp/github.py
CHANGED
|
@@ -14,12 +14,27 @@ import requests
|
|
|
14
14
|
from fastmcp import FastMCP
|
|
15
15
|
from pydantic import BaseModel, Field
|
|
16
16
|
|
|
17
|
-
from airbyte_ops_mcp.github_actions import
|
|
17
|
+
from airbyte_ops_mcp.github_actions import (
|
|
18
|
+
GITHUB_API_BASE,
|
|
19
|
+
get_workflow_jobs,
|
|
20
|
+
resolve_github_token,
|
|
21
|
+
)
|
|
18
22
|
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
19
23
|
|
|
20
24
|
DOCKERHUB_API_BASE = "https://hub.docker.com/v2"
|
|
21
25
|
|
|
22
26
|
|
|
27
|
+
class JobInfo(BaseModel):
|
|
28
|
+
"""Information about a single job in a workflow run."""
|
|
29
|
+
|
|
30
|
+
job_id: int
|
|
31
|
+
name: str
|
|
32
|
+
status: str
|
|
33
|
+
conclusion: str | None = None
|
|
34
|
+
started_at: str | None = None
|
|
35
|
+
completed_at: str | None = None
|
|
36
|
+
|
|
37
|
+
|
|
23
38
|
class WorkflowRunStatus(BaseModel):
|
|
24
39
|
"""Response model for check_workflow_status MCP tool."""
|
|
25
40
|
|
|
@@ -34,6 +49,7 @@ class WorkflowRunStatus(BaseModel):
|
|
|
34
49
|
updated_at: str
|
|
35
50
|
run_started_at: str | None = None
|
|
36
51
|
jobs_url: str
|
|
52
|
+
jobs: list[JobInfo] = []
|
|
37
53
|
|
|
38
54
|
|
|
39
55
|
def _parse_workflow_url(url: str) -> tuple[str, str, int]:
|
|
@@ -148,6 +164,22 @@ def check_workflow_status(
|
|
|
148
164
|
# Get workflow run details
|
|
149
165
|
run_data = _get_workflow_run(owner, repo, run_id, token)
|
|
150
166
|
|
|
167
|
+
# Get jobs for the workflow run (uses upstream function that resolves its own token)
|
|
168
|
+
workflow_jobs = get_workflow_jobs(owner, repo, run_id)
|
|
169
|
+
|
|
170
|
+
# Convert dataclass objects to Pydantic models for the response
|
|
171
|
+
jobs = [
|
|
172
|
+
JobInfo(
|
|
173
|
+
job_id=job.job_id,
|
|
174
|
+
name=job.name,
|
|
175
|
+
status=job.status,
|
|
176
|
+
conclusion=job.conclusion,
|
|
177
|
+
started_at=job.started_at,
|
|
178
|
+
completed_at=job.completed_at,
|
|
179
|
+
)
|
|
180
|
+
for job in workflow_jobs
|
|
181
|
+
]
|
|
182
|
+
|
|
151
183
|
return WorkflowRunStatus(
|
|
152
184
|
run_id=run_data["id"],
|
|
153
185
|
status=run_data["status"],
|
|
@@ -160,6 +192,7 @@ def check_workflow_status(
|
|
|
160
192
|
updated_at=run_data["updated_at"],
|
|
161
193
|
run_started_at=run_data.get("run_started_at"),
|
|
162
194
|
jobs_url=run_data["jobs_url"],
|
|
195
|
+
jobs=jobs,
|
|
163
196
|
)
|
|
164
197
|
|
|
165
198
|
|
|
@@ -20,7 +20,7 @@ from airbyte_ops_mcp.prod_db_access.queries import (
|
|
|
20
20
|
query_connections_by_connector,
|
|
21
21
|
query_connector_versions,
|
|
22
22
|
query_dataplanes_list,
|
|
23
|
-
|
|
23
|
+
query_failed_sync_attempts_for_connector,
|
|
24
24
|
query_new_connector_releases,
|
|
25
25
|
query_sync_results_for_version,
|
|
26
26
|
query_workspace_info,
|
|
@@ -249,12 +249,41 @@ def query_prod_connector_version_sync_results(
|
|
|
249
249
|
@mcp_tool(
|
|
250
250
|
read_only=True,
|
|
251
251
|
idempotent=True,
|
|
252
|
+
open_world=True,
|
|
252
253
|
)
|
|
253
|
-
def
|
|
254
|
-
|
|
255
|
-
str,
|
|
256
|
-
Field(
|
|
257
|
-
|
|
254
|
+
def query_prod_failed_sync_attempts_for_connector(
|
|
255
|
+
source_definition_id: Annotated[
|
|
256
|
+
str | None,
|
|
257
|
+
Field(
|
|
258
|
+
description=(
|
|
259
|
+
"Source connector definition ID (UUID) to search for. "
|
|
260
|
+
"Exactly one of this or source_canonical_name is required. "
|
|
261
|
+
"Example: 'afa734e4-3571-11ec-991a-1e0031268139' for YouTube Analytics."
|
|
262
|
+
),
|
|
263
|
+
default=None,
|
|
264
|
+
),
|
|
265
|
+
] = None,
|
|
266
|
+
source_canonical_name: Annotated[
|
|
267
|
+
str | None,
|
|
268
|
+
Field(
|
|
269
|
+
description=(
|
|
270
|
+
"Canonical source connector name to search for. "
|
|
271
|
+
"Exactly one of this or source_definition_id is required. "
|
|
272
|
+
"Examples: 'source-youtube-analytics', 'YouTube Analytics'."
|
|
273
|
+
),
|
|
274
|
+
default=None,
|
|
275
|
+
),
|
|
276
|
+
] = None,
|
|
277
|
+
organization_id: Annotated[
|
|
278
|
+
str | None,
|
|
279
|
+
Field(
|
|
280
|
+
description=(
|
|
281
|
+
"Optional organization ID (UUID) to filter results. "
|
|
282
|
+
"If provided, only failed attempts from this organization will be returned."
|
|
283
|
+
),
|
|
284
|
+
default=None,
|
|
285
|
+
),
|
|
286
|
+
] = None,
|
|
258
287
|
days: Annotated[
|
|
259
288
|
int,
|
|
260
289
|
Field(description="Number of days to look back (default: 7)", default=7),
|
|
@@ -264,29 +293,43 @@ def query_prod_failed_sync_attempts_for_version(
|
|
|
264
293
|
Field(description="Maximum number of results (default: 100)", default=100),
|
|
265
294
|
] = 100,
|
|
266
295
|
) -> list[dict[str, Any]]:
|
|
267
|
-
"""List failed sync attempts
|
|
296
|
+
"""List failed sync attempts for ALL actors using a source connector type.
|
|
268
297
|
|
|
269
|
-
|
|
270
|
-
|
|
298
|
+
This tool finds all actors with the given connector definition and returns their
|
|
299
|
+
failed sync attempts, regardless of whether they have explicit version pins.
|
|
271
300
|
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
- failed_attempt_number: Which attempt this was (0-indexed)
|
|
276
|
-
- failure_summary: JSON containing failure details including failureType and messages
|
|
301
|
+
This is useful for investigating connector issues across all users. Use this when
|
|
302
|
+
you want to find failures for a connector type regardless of which version users
|
|
303
|
+
are on.
|
|
277
304
|
|
|
278
|
-
Note:
|
|
279
|
-
|
|
305
|
+
Note: This tool only supports SOURCE connectors. For destination connectors,
|
|
306
|
+
a separate tool would be needed.
|
|
280
307
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
organization_id, dataplane_group_id, dataplane_name, failed_attempt_id,
|
|
285
|
-
failed_attempt_number, failed_attempt_status, failed_attempt_created_at,
|
|
286
|
-
failed_attempt_ended_at, failure_summary, processing_task_queue
|
|
308
|
+
Key fields in results:
|
|
309
|
+
- failure_summary: JSON containing failure details including failureType and messages
|
|
310
|
+
- pin_origin_type, pin_origin, pinned_version_id: Version pin context (NULL if not pinned)
|
|
287
311
|
"""
|
|
288
|
-
|
|
289
|
-
|
|
312
|
+
# Validate that exactly one of the two parameters is provided
|
|
313
|
+
if (source_definition_id is None) == (source_canonical_name is None):
|
|
314
|
+
raise PyAirbyteInputError(
|
|
315
|
+
message=(
|
|
316
|
+
"Exactly one of source_definition_id or source_canonical_name "
|
|
317
|
+
"must be provided, but not both."
|
|
318
|
+
),
|
|
319
|
+
)
|
|
320
|
+
|
|
321
|
+
# Resolve canonical name to definition ID if needed
|
|
322
|
+
resolved_definition_id: str
|
|
323
|
+
if source_canonical_name:
|
|
324
|
+
resolved_definition_id = _resolve_canonical_name_to_definition_id(
|
|
325
|
+
canonical_name=source_canonical_name,
|
|
326
|
+
)
|
|
327
|
+
else:
|
|
328
|
+
resolved_definition_id = source_definition_id # type: ignore[assignment]
|
|
329
|
+
|
|
330
|
+
return query_failed_sync_attempts_for_connector(
|
|
331
|
+
connector_definition_id=resolved_definition_id,
|
|
332
|
+
organization_id=organization_id,
|
|
290
333
|
days=days,
|
|
291
334
|
limit=limit,
|
|
292
335
|
)
|