airbyte-internal-ops 0.3.0__py3-none-any.whl → 0.4.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.
@@ -7,7 +7,8 @@ airbyte_ops_mcp.prod_db_access.queries for use by AI agents.
7
7
 
8
8
  from __future__ import annotations
9
9
 
10
- from datetime import datetime
10
+ from datetime import datetime, timezone
11
+ from enum import StrEnum
11
12
  from typing import Annotated, Any
12
13
 
13
14
  import requests
@@ -23,13 +24,25 @@ from airbyte_ops_mcp.prod_db_access.queries import (
23
24
  query_connections_by_destination_connector,
24
25
  query_connector_versions,
25
26
  query_dataplanes_list,
27
+ query_destination_connection_stats,
26
28
  query_failed_sync_attempts_for_connector,
27
29
  query_new_connector_releases,
28
- query_sync_results_for_version,
30
+ query_recent_syncs_for_connector,
31
+ query_source_connection_stats,
32
+ query_syncs_for_version_pinned_connector,
29
33
  query_workspace_info,
30
34
  query_workspaces_by_email_domain,
31
35
  )
32
36
 
37
+
38
+ class StatusFilter(StrEnum):
39
+ """Filter for job status in sync queries."""
40
+
41
+ ALL = "all"
42
+ SUCCEEDED = "succeeded"
43
+ FAILED = "failed"
44
+
45
+
33
46
  # Cloud UI base URL for building connection URLs
34
47
  CLOUD_UI_BASE_URL = "https://cloud.airbyte.com"
35
48
 
@@ -79,6 +92,94 @@ class WorkspacesByEmailDomainResult(BaseModel):
79
92
  )
80
93
 
81
94
 
95
+ class LatestAttemptBreakdown(BaseModel):
96
+ """Breakdown of connections by latest attempt status."""
97
+
98
+ succeeded: int = Field(
99
+ default=0, description="Connections where latest attempt succeeded"
100
+ )
101
+ failed: int = Field(
102
+ default=0, description="Connections where latest attempt failed"
103
+ )
104
+ cancelled: int = Field(
105
+ default=0, description="Connections where latest attempt was cancelled"
106
+ )
107
+ running: int = Field(
108
+ default=0, description="Connections where latest attempt is still running"
109
+ )
110
+ unknown: int = Field(
111
+ default=0,
112
+ description="Connections with no recent attempts in the lookback window",
113
+ )
114
+
115
+
116
+ class VersionPinStats(BaseModel):
117
+ """Stats for connections pinned to a specific version."""
118
+
119
+ pinned_version_id: str | None = Field(
120
+ description="The connector version UUID (None for unpinned connections)"
121
+ )
122
+ docker_image_tag: str | None = Field(
123
+ default=None, description="The docker image tag for this version"
124
+ )
125
+ total_connections: int = Field(description="Total number of connections")
126
+ enabled_connections: int = Field(
127
+ description="Number of enabled (active status) connections"
128
+ )
129
+ active_connections: int = Field(
130
+ description="Number of connections with recent sync activity"
131
+ )
132
+ latest_attempt: LatestAttemptBreakdown = Field(
133
+ description="Breakdown by latest attempt status"
134
+ )
135
+
136
+
137
+ class ConnectorConnectionStats(BaseModel):
138
+ """Aggregate connection stats for a connector."""
139
+
140
+ connector_definition_id: str = Field(description="The connector definition UUID")
141
+ connector_type: str = Field(description="'source' or 'destination'")
142
+ canonical_name: str | None = Field(
143
+ default=None, description="The canonical connector name if resolved"
144
+ )
145
+ total_connections: int = Field(
146
+ description="Total number of non-deprecated connections"
147
+ )
148
+ enabled_connections: int = Field(
149
+ description="Number of enabled (active status) connections"
150
+ )
151
+ active_connections: int = Field(
152
+ description="Number of connections with recent sync activity"
153
+ )
154
+ pinned_connections: int = Field(
155
+ description="Number of connections with explicit version pins"
156
+ )
157
+ unpinned_connections: int = Field(
158
+ description="Number of connections on default version"
159
+ )
160
+ latest_attempt: LatestAttemptBreakdown = Field(
161
+ description="Overall breakdown by latest attempt status"
162
+ )
163
+ by_version: list[VersionPinStats] = Field(
164
+ description="Stats broken down by pinned version"
165
+ )
166
+
167
+
168
+ class ConnectorConnectionStatsResponse(BaseModel):
169
+ """Response containing connection stats for multiple connectors."""
170
+
171
+ sources: list[ConnectorConnectionStats] = Field(
172
+ default_factory=list, description="Stats for source connectors"
173
+ )
174
+ destinations: list[ConnectorConnectionStats] = Field(
175
+ default_factory=list, description="Stats for destination connectors"
176
+ )
177
+ active_within_days: int = Field(
178
+ description="Lookback window used for 'active' connections"
179
+ )
180
+ generated_at: datetime = Field(description="When this response was generated")
181
+
182
+
82
183
  # Cloud registry URL for resolving canonical names
83
184
  CLOUD_REGISTRY_URL = (
84
185
  "https://connectors.airbyte.com/files/registries/v0/cloud_registry.json"
@@ -293,7 +394,7 @@ def query_prod_actors_by_connector_version(
293
394
  read_only=True,
294
395
  idempotent=True,
295
396
  )
296
- def query_prod_connector_version_sync_results(
397
+ def query_prod_recent_syncs_for_version_pinned_connector(
297
398
  connector_version_id: Annotated[
298
399
  str,
299
400
  Field(description="Connector version UUID to find sync results for"),
@@ -314,11 +415,16 @@ def query_prod_connector_version_sync_results(
314
415
  ),
315
416
  ] = False,
316
417
  ) -> list[dict[str, Any]]:
317
- """List sync job results for actors pinned to a specific connector version.
418
+ """List sync job results for actors PINNED to a specific connector version.
419
+
420
+ IMPORTANT: This tool ONLY returns results for actors that have been explicitly
421
+ pinned to the specified version via scoped_configuration. Most connections run
422
+ unpinned and will NOT appear in these results.
318
423
 
319
- Returns sync job results for connections using actors that are pinned
320
- to the specified version. Useful for monitoring rollout health and
321
- identifying issues with specific connector versions.
424
+ Use this tool when you want to monitor rollout health for actors that have been
425
+ explicitly pinned to a pre-release or specific version. For finding healthy
426
+ connections across ALL actors using a connector type (regardless of pinning),
427
+ use query_prod_recent_syncs_for_connector instead.
322
428
 
323
429
  The actor_id field is the actor ID (superset of source_id/destination_id).
324
430
 
@@ -327,7 +433,7 @@ def query_prod_connector_version_sync_results(
327
433
  pin_origin_type, pin_origin, workspace_id, workspace_name, organization_id,
328
434
  dataplane_group_id, dataplane_name
329
435
  """
330
- return query_sync_results_for_version(
436
+ return query_syncs_for_version_pinned_connector(
331
437
  connector_version_id,
332
438
  days=days,
333
439
  limit=limit,
@@ -335,6 +441,163 @@ def query_prod_connector_version_sync_results(
335
441
  )
336
442
 
337
443
 
444
+ @mcp_tool(
445
+ read_only=True,
446
+ idempotent=True,
447
+ open_world=True,
448
+ )
449
+ def query_prod_recent_syncs_for_connector(
450
+ source_definition_id: Annotated[
451
+ str | None,
452
+ Field(
453
+ description=(
454
+ "Source connector definition ID (UUID) to search for. "
455
+ "Provide this OR source_canonical_name OR destination_definition_id "
456
+ "OR destination_canonical_name (exactly one required). "
457
+ "Example: 'afa734e4-3571-11ec-991a-1e0031268139' for YouTube Analytics."
458
+ ),
459
+ default=None,
460
+ ),
461
+ ],
462
+ source_canonical_name: Annotated[
463
+ str | None,
464
+ Field(
465
+ description=(
466
+ "Canonical source connector name to search for. "
467
+ "Provide this OR source_definition_id OR destination_definition_id "
468
+ "OR destination_canonical_name (exactly one required). "
469
+ "Examples: 'source-youtube-analytics', 'YouTube Analytics'."
470
+ ),
471
+ default=None,
472
+ ),
473
+ ],
474
+ destination_definition_id: Annotated[
475
+ str | None,
476
+ Field(
477
+ description=(
478
+ "Destination connector definition ID (UUID) to search for. "
479
+ "Provide this OR destination_canonical_name OR source_definition_id "
480
+ "OR source_canonical_name (exactly one required). "
481
+ "Example: '94bd199c-2ff0-4aa2-b98e-17f0acb72610' for DuckDB."
482
+ ),
483
+ default=None,
484
+ ),
485
+ ],
486
+ destination_canonical_name: Annotated[
487
+ str | None,
488
+ Field(
489
+ description=(
490
+ "Canonical destination connector name to search for. "
491
+ "Provide this OR destination_definition_id OR source_definition_id "
492
+ "OR source_canonical_name (exactly one required). "
493
+ "Examples: 'destination-duckdb', 'DuckDB'."
494
+ ),
495
+ default=None,
496
+ ),
497
+ ],
498
+ status_filter: Annotated[
499
+ StatusFilter,
500
+ Field(
501
+ description=(
502
+ "Filter by job status: 'all' (default), 'succeeded', or 'failed'. "
503
+ "Use 'succeeded' to find healthy connections with recent successful syncs. "
504
+ "Use 'failed' to find connections with recent failures."
505
+ ),
506
+ default=StatusFilter.ALL,
507
+ ),
508
+ ],
509
+ organization_id: Annotated[
510
+ str | OrganizationAliasEnum | None,
511
+ Field(
512
+ description=(
513
+ "Optional organization ID (UUID) or alias to filter results. "
514
+ "If provided, only syncs from this organization will be returned. "
515
+ "Accepts '@airbyte-internal' as an alias for the Airbyte internal org."
516
+ ),
517
+ default=None,
518
+ ),
519
+ ],
520
+ lookback_days: Annotated[
521
+ int,
522
+ Field(description="Number of days to look back (default: 7)", default=7),
523
+ ],
524
+ limit: Annotated[
525
+ int,
526
+ Field(description="Maximum number of results (default: 100)", default=100),
527
+ ],
528
+ ) -> list[dict[str, Any]]:
529
+ """List recent sync jobs for ALL actors using a connector type.
530
+
531
+ This tool finds all actors with the given connector definition and returns their
532
+ recent sync jobs, regardless of whether they have explicit version pins. It filters
533
+ out deleted actors, deleted workspaces, and deprecated connections.
534
+
535
+ Use this tool to:
536
+ - Find healthy connections with recent successful syncs (status_filter='succeeded')
537
+ - Investigate connector issues across all users (status_filter='failed')
538
+ - Get an overview of all recent sync activity (status_filter='all')
539
+
540
+ Supports both SOURCE and DESTINATION connectors. Provide exactly one of:
541
+ source_definition_id, source_canonical_name, destination_definition_id,
542
+ or destination_canonical_name.
543
+
544
+ Key fields in results:
545
+ - job_status: 'succeeded', 'failed', 'cancelled', etc.
546
+ - connection_id, connection_name: The connection that ran the sync
547
+ - actor_id, actor_name: The source or destination actor
548
+ - pin_origin_type, pin_origin, pinned_version_id: Version pin context (NULL if not pinned)
549
+ """
550
+ # Validate that exactly one connector parameter is provided
551
+ provided_params = [
552
+ source_definition_id,
553
+ source_canonical_name,
554
+ destination_definition_id,
555
+ destination_canonical_name,
556
+ ]
557
+ num_provided = sum(p is not None for p in provided_params)
558
+ if num_provided != 1:
559
+ raise PyAirbyteInputError(
560
+ message=(
561
+ "Exactly one of source_definition_id, source_canonical_name, "
562
+ "destination_definition_id, or destination_canonical_name must be provided."
563
+ ),
564
+ )
565
+
566
+ # Determine if this is a destination connector
567
+ is_destination = (
568
+ destination_definition_id is not None or destination_canonical_name is not None
569
+ )
570
+
571
+ # Resolve canonical name to definition ID if needed
572
+ resolved_definition_id: str
573
+ if source_canonical_name:
574
+ resolved_definition_id = _resolve_canonical_name_to_definition_id(
575
+ canonical_name=source_canonical_name,
576
+ )
577
+ elif destination_canonical_name:
578
+ resolved_definition_id = _resolve_canonical_name_to_definition_id(
579
+ canonical_name=destination_canonical_name,
580
+ )
581
+ elif source_definition_id:
582
+ resolved_definition_id = source_definition_id
583
+ else:
584
+ # We've validated exactly one param is provided, so this must be set
585
+ assert destination_definition_id is not None
586
+ resolved_definition_id = destination_definition_id
587
+
588
+ # Resolve organization ID alias
589
+ resolved_organization_id = OrganizationAliasEnum.resolve(organization_id)
590
+
591
+ return query_recent_syncs_for_connector(
592
+ connector_definition_id=resolved_definition_id,
593
+ is_destination=is_destination,
594
+ status_filter=status_filter,
595
+ organization_id=resolved_organization_id,
596
+ days=lookback_days,
597
+ limit=limit,
598
+ )
599
+
600
+
338
601
  @mcp_tool(
339
602
  read_only=True,
340
603
  idempotent=True,
@@ -678,6 +941,213 @@ def query_prod_workspaces_by_email_domain(
678
941
  )
679
942
 
680
943
 
944
+ def _build_connector_stats(
945
+ connector_definition_id: str,
946
+ connector_type: str,
947
+ canonical_name: str | None,
948
+ rows: list[dict[str, Any]],
949
+ version_tags: dict[str, str | None],
950
+ ) -> ConnectorConnectionStats:
951
+ """Build ConnectorConnectionStats from query result rows."""
952
+ # Aggregate totals across all version groups
953
+ total_connections = 0
954
+ enabled_connections = 0
955
+ active_connections = 0
956
+ pinned_connections = 0
957
+ unpinned_connections = 0
958
+ total_succeeded = 0
959
+ total_failed = 0
960
+ total_cancelled = 0
961
+ total_running = 0
962
+ total_unknown = 0
963
+
964
+ by_version: list[VersionPinStats] = []
965
+
966
+ for row in rows:
967
+ version_id = row.get("pinned_version_id")
968
+ row_total = int(row.get("total_connections", 0))
969
+ row_enabled = int(row.get("enabled_connections", 0))
970
+ row_active = int(row.get("active_connections", 0))
971
+ row_pinned = int(row.get("pinned_connections", 0))
972
+ row_unpinned = int(row.get("unpinned_connections", 0))
973
+ row_succeeded = int(row.get("succeeded_connections", 0))
974
+ row_failed = int(row.get("failed_connections", 0))
975
+ row_cancelled = int(row.get("cancelled_connections", 0))
976
+ row_running = int(row.get("running_connections", 0))
977
+ row_unknown = int(row.get("unknown_connections", 0))
978
+
979
+ total_connections += row_total
980
+ enabled_connections += row_enabled
981
+ active_connections += row_active
982
+ pinned_connections += row_pinned
983
+ unpinned_connections += row_unpinned
984
+ total_succeeded += row_succeeded
985
+ total_failed += row_failed
986
+ total_cancelled += row_cancelled
987
+ total_running += row_running
988
+ total_unknown += row_unknown
989
+
990
+ by_version.append(
991
+ VersionPinStats(
992
+ pinned_version_id=str(version_id) if version_id else None,
993
+ docker_image_tag=version_tags.get(str(version_id))
994
+ if version_id
995
+ else None,
996
+ total_connections=row_total,
997
+ enabled_connections=row_enabled,
998
+ active_connections=row_active,
999
+ latest_attempt=LatestAttemptBreakdown(
1000
+ succeeded=row_succeeded,
1001
+ failed=row_failed,
1002
+ cancelled=row_cancelled,
1003
+ running=row_running,
1004
+ unknown=row_unknown,
1005
+ ),
1006
+ )
1007
+ )
1008
+
1009
+ return ConnectorConnectionStats(
1010
+ connector_definition_id=connector_definition_id,
1011
+ connector_type=connector_type,
1012
+ canonical_name=canonical_name,
1013
+ total_connections=total_connections,
1014
+ enabled_connections=enabled_connections,
1015
+ active_connections=active_connections,
1016
+ pinned_connections=pinned_connections,
1017
+ unpinned_connections=unpinned_connections,
1018
+ latest_attempt=LatestAttemptBreakdown(
1019
+ succeeded=total_succeeded,
1020
+ failed=total_failed,
1021
+ cancelled=total_cancelled,
1022
+ running=total_running,
1023
+ unknown=total_unknown,
1024
+ ),
1025
+ by_version=by_version,
1026
+ )
1027
+
1028
+
1029
+ @mcp_tool(
1030
+ read_only=True,
1031
+ idempotent=True,
1032
+ open_world=True,
1033
+ )
1034
+ def query_prod_connector_connection_stats(
1035
+ source_definition_ids: Annotated[
1036
+ list[str] | None,
1037
+ Field(
1038
+ description=(
1039
+ "List of source connector definition IDs (UUIDs) to get stats for. "
1040
+ "Example: ['afa734e4-3571-11ec-991a-1e0031268139']"
1041
+ ),
1042
+ default=None,
1043
+ ),
1044
+ ] = None,
1045
+ destination_definition_ids: Annotated[
1046
+ list[str] | None,
1047
+ Field(
1048
+ description=(
1049
+ "List of destination connector definition IDs (UUIDs) to get stats for. "
1050
+ "Example: ['94bd199c-2ff0-4aa2-b98e-17f0acb72610']"
1051
+ ),
1052
+ default=None,
1053
+ ),
1054
+ ] = None,
1055
+ active_within_days: Annotated[
1056
+ int,
1057
+ Field(
1058
+ description=(
1059
+ "Number of days to look back for 'active' connections (default: 7). "
1060
+ "Connections with sync activity within this window are counted as active."
1061
+ ),
1062
+ default=7,
1063
+ ),
1064
+ ] = 7,
1065
+ ) -> ConnectorConnectionStatsResponse:
1066
+ """Get aggregate connection stats for multiple connectors.
1067
+
1068
+ Returns counts of connections grouped by pinned version for each connector,
1069
+ including:
1070
+ - Total, enabled, and active connection counts
1071
+ - Pinned vs unpinned breakdown
1072
+ - Latest attempt status breakdown (succeeded, failed, cancelled, running, unknown)
1073
+
1074
+ This tool is designed for release monitoring workflows. It allows you to:
1075
+ 1. Query recently released connectors to identify which ones to monitor
1076
+ 2. Get aggregate stats showing how many connections are using each version
1077
+ 3. See health metrics (pass/fail) broken down by version
1078
+
1079
+ The 'active_within_days' parameter controls the lookback window for:
1080
+ - Counting 'active' connections (those with recent sync activity)
1081
+ - Determining 'latest attempt status' (most recent attempt within the window)
1082
+
1083
+ Connections with no sync activity in the lookback window will have
1084
+ 'unknown' status in the latest_attempt breakdown.
1085
+ """
1086
+ # Initialize empty lists if None
1087
+ source_ids = source_definition_ids or []
1088
+ destination_ids = destination_definition_ids or []
1089
+
1090
+ if not source_ids and not destination_ids:
1091
+ raise PyAirbyteInputError(
1092
+ message=(
1093
+ "At least one of source_definition_ids or destination_definition_ids "
1094
+ "must be provided."
1095
+ ),
1096
+ )
1097
+
1098
+ sources: list[ConnectorConnectionStats] = []
1099
+ destinations: list[ConnectorConnectionStats] = []
1100
+
1101
+ # Process source connectors
1102
+ for source_def_id in source_ids:
1103
+ # Get version info for tag lookup
1104
+ versions = query_connector_versions(source_def_id)
1105
+ version_tags = {
1106
+ str(v["version_id"]): v.get("docker_image_tag") for v in versions
1107
+ }
1108
+
1109
+ # Get aggregate stats
1110
+ rows = query_source_connection_stats(source_def_id, days=active_within_days)
1111
+
1112
+ sources.append(
1113
+ _build_connector_stats(
1114
+ connector_definition_id=source_def_id,
1115
+ connector_type="source",
1116
+ canonical_name=None,
1117
+ rows=rows,
1118
+ version_tags=version_tags,
1119
+ )
1120
+ )
1121
+
1122
+ # Process destination connectors
1123
+ for dest_def_id in destination_ids:
1124
+ # Get version info for tag lookup
1125
+ versions = query_connector_versions(dest_def_id)
1126
+ version_tags = {
1127
+ str(v["version_id"]): v.get("docker_image_tag") for v in versions
1128
+ }
1129
+
1130
+ # Get aggregate stats
1131
+ rows = query_destination_connection_stats(dest_def_id, days=active_within_days)
1132
+
1133
+ destinations.append(
1134
+ _build_connector_stats(
1135
+ connector_definition_id=dest_def_id,
1136
+ connector_type="destination",
1137
+ canonical_name=None,
1138
+ rows=rows,
1139
+ version_tags=version_tags,
1140
+ )
1141
+ )
1142
+
1143
+ return ConnectorConnectionStatsResponse(
1144
+ sources=sources,
1145
+ destinations=destinations,
1146
+ active_within_days=active_within_days,
1147
+ generated_at=datetime.now(timezone.utc),
1148
+ )
1149
+
1150
+
681
1151
  def register_prod_db_query_tools(app: FastMCP) -> None:
682
1152
  """Register prod DB query tools with the FastMCP app."""
683
1153
  register_mcp_tools(app, domain=__name__)
@@ -37,6 +37,7 @@ from airbyte_ops_mcp.github_actions import (
37
37
  trigger_workflow_dispatch,
38
38
  )
39
39
  from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
40
+ from airbyte_ops_mcp.mcp.prerelease import ConnectorRepo
40
41
 
41
42
  logger = logging.getLogger(__name__)
42
43
 
@@ -309,7 +310,13 @@ def run_regression_tests(
309
310
  ],
310
311
  pr: Annotated[
311
312
  int,
312
- "PR number from the airbyte monorepo to checkout and build from (e.g., 70847). Required.",
313
+ "PR number to checkout and build from (e.g., 70847). Required. "
314
+ "The PR must be from the repository specified by the 'repo' parameter.",
315
+ ],
316
+ repo: Annotated[
317
+ ConnectorRepo,
318
+ "Repository where the connector PR is located. "
319
+ "Use 'airbyte' for OSS connectors (default) or 'airbyte-enterprise' for enterprise connectors.",
313
320
  ],
314
321
  connection_id: Annotated[
315
322
  str | None,
@@ -347,6 +354,10 @@ def run_regression_tests(
347
354
  This tool triggers the regression test workflow which builds the connector
348
355
  from the specified PR and runs tests against it.
349
356
 
357
+ Supports both OSS connectors (from airbytehq/airbyte) and enterprise connectors
358
+ (from airbytehq/airbyte-enterprise). Use the 'repo' parameter to specify which
359
+ repository contains the connector PR.
360
+
350
361
  - skip_compare=False (default): Comparison mode - compares the PR version
351
362
  against the baseline (control) version.
352
363
  - skip_compare=True: Single-version mode - runs tests without comparison.
@@ -390,10 +401,11 @@ def run_regression_tests(
390
401
  workflow_url=None,
391
402
  )
392
403
 
393
- # Build workflow inputs - connector_name and pr are required
404
+ # Build workflow inputs - connector_name, pr, and repo are required
394
405
  workflow_inputs: dict[str, str] = {
395
406
  "connector_name": connector_name,
396
407
  "pr": str(pr),
408
+ "repo": repo,
397
409
  }
398
410
 
399
411
  # Add optional inputs
@@ -431,12 +443,13 @@ def run_regression_tests(
431
443
 
432
444
  view_url = dispatch_result.run_url or dispatch_result.workflow_url
433
445
  connection_info = f" for connection {connection_id}" if connection_id else ""
446
+ repo_info = f" from {repo}" if repo != ConnectorRepo.AIRBYTE else ""
434
447
  return RunRegressionTestsResponse(
435
448
  run_id=run_id,
436
449
  status=TestRunStatus.QUEUED,
437
450
  message=(
438
451
  f"{mode_description.capitalize()} regression test workflow triggered "
439
- f"for {connector_name} (PR #{pr}){connection_info}. View progress at: {view_url}"
452
+ f"for {connector_name} (PR #{pr}{repo_info}){connection_info}. View progress at: {view_url}"
440
453
  ),
441
454
  workflow_url=dispatch_result.workflow_url,
442
455
  github_run_id=dispatch_result.run_id,