airbyte-internal-ops 0.1.7__py3-none-any.whl → 0.1.8__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.7.dist-info → airbyte_internal_ops-0.1.8.dist-info}/METADATA +1 -1
- {airbyte_internal_ops-0.1.7.dist-info → airbyte_internal_ops-0.1.8.dist-info}/RECORD +18 -17
- airbyte_ops_mcp/connection_config_retriever/retrieval.py +2 -1
- airbyte_ops_mcp/constants.py +4 -0
- airbyte_ops_mcp/gcp_auth.py +99 -0
- airbyte_ops_mcp/live_tests/connection_secret_retriever.py +2 -36
- airbyte_ops_mcp/mcp/_mcp_utils.py +89 -20
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +2 -4
- airbyte_ops_mcp/mcp/github.py +2 -4
- airbyte_ops_mcp/mcp/github_repo_ops.py +2 -4
- airbyte_ops_mcp/mcp/live_tests.py +2 -3
- airbyte_ops_mcp/mcp/prerelease.py +2 -3
- airbyte_ops_mcp/mcp/prod_db_queries.py +2 -9
- airbyte_ops_mcp/mcp/prompts.py +2 -3
- airbyte_ops_mcp/mcp/server_info.py +1 -3
- airbyte_ops_mcp/prod_db_access/queries.py +2 -1
- {airbyte_internal_ops-0.1.7.dist-info → airbyte_internal_ops-0.1.8.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.1.7.dist-info → airbyte_internal_ops-0.1.8.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
airbyte_ops_mcp/__init__.py,sha256=HhzURuYr29_UIdMrnWYaZB8ENr_kFkBdm4uqeiIW3Vw,760
|
|
2
2
|
airbyte_ops_mcp/_annotations.py,sha256=MO-SBDnbykxxHDESG7d8rviZZ4WlZgJKv0a8eBqcEzQ,1757
|
|
3
|
-
airbyte_ops_mcp/constants.py,sha256=
|
|
3
|
+
airbyte_ops_mcp/constants.py,sha256=419-AlRfwbbxeEEV9lhmXhpTUjsSdzJpfcuL_MZZtXM,1982
|
|
4
|
+
airbyte_ops_mcp/gcp_auth.py,sha256=5k-k145ZoYhHLjyDES8nrA8f8BBihRI0ykrdD1IcfOs,3599
|
|
4
5
|
airbyte_ops_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
6
|
airbyte_ops_mcp/_legacy/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
7
|
airbyte_ops_mcp/_legacy/airbyte_ci/README.md,sha256=qEYx4geDR8AEDjrcA303h7Nol-CMDLojxUyiGzQprM8,236
|
|
@@ -361,13 +362,13 @@ airbyte_ops_mcp/cloud_admin/connection_config.py,sha256=UtbIwuB7CA3WJr9oYRwlKDsj
|
|
|
361
362
|
airbyte_ops_mcp/cloud_admin/models.py,sha256=YZ3FbEW-tZa50khKTTl4Bzvy_LsGyyQd6qcpXo62jls,2670
|
|
362
363
|
airbyte_ops_mcp/connection_config_retriever/__init__.py,sha256=Xoi-YvARrNPhECdpwEDDkdwEpnvj8zuUlwULpf4iRrU,800
|
|
363
364
|
airbyte_ops_mcp/connection_config_retriever/audit_logging.py,sha256=GjT4dVa0TtvGDmiBz9qwzcYCnSf9hTo7UM6l7ubUNE8,2846
|
|
364
|
-
airbyte_ops_mcp/connection_config_retriever/retrieval.py,sha256=
|
|
365
|
+
airbyte_ops_mcp/connection_config_retriever/retrieval.py,sha256=s6yeCyrboWkUd6KdaheEo87x-rLtQNTL8XeR8O9z2HI,12160
|
|
365
366
|
airbyte_ops_mcp/connection_config_retriever/secrets_resolution.py,sha256=12g0lZzhCzAPl4Iv4eMW6d76mvXjIBGspOnNhywzks4,3644
|
|
366
367
|
airbyte_ops_mcp/live_tests/__init__.py,sha256=qJac67dt6DQCqif39HqeiG3Tr9xrxfP-ala8HsLZKis,1020
|
|
367
368
|
airbyte_ops_mcp/live_tests/ci_output.py,sha256=NQATOGid0OCbLEl2NOtGK4cHLL5OxXhjmtanBjIlCyE,11369
|
|
368
369
|
airbyte_ops_mcp/live_tests/config.py,sha256=dwWeY0tatdbwl9BqbhZ7EljoZDCtKmGO5fvOAIxeXmA,5873
|
|
369
370
|
airbyte_ops_mcp/live_tests/connection_fetcher.py,sha256=5wIiA0VvCFNEc-fr6Po18gZMX3E5fyPOGf2SuVOqv5U,12799
|
|
370
|
-
airbyte_ops_mcp/live_tests/connection_secret_retriever.py,sha256=
|
|
371
|
+
airbyte_ops_mcp/live_tests/connection_secret_retriever.py,sha256=DtZYB4Y8CXfUXTFhmzrqzjuEFoICzz5Md3Ol_y9HCq0,4861
|
|
371
372
|
airbyte_ops_mcp/live_tests/connector_runner.py,sha256=fGE_TCii9zhC3pbyBupJ3JVkuxOWB59Q1DgigcF3q04,9707
|
|
372
373
|
airbyte_ops_mcp/live_tests/evaluation_modes.py,sha256=lAL6pEDmy_XCC7_m4_NXjt_f6Z8CXeAhMkc0FU8bm_M,1364
|
|
373
374
|
airbyte_ops_mcp/live_tests/http_metrics.py,sha256=oTD7f2MnQOvx4plOxHop2bInQ0-whvuToSsrC7TIM-M,12469
|
|
@@ -384,29 +385,29 @@ airbyte_ops_mcp/live_tests/validation/catalog_validators.py,sha256=jqqVAMOk0mtdP
|
|
|
384
385
|
airbyte_ops_mcp/live_tests/validation/record_validators.py,sha256=-7Ir2LWGCrtadK2JLuBgppSyk0RFJX6Nsy0lrabtwrs,7411
|
|
385
386
|
airbyte_ops_mcp/mcp/__init__.py,sha256=QqkNkxzdXlg-W03urBAQ3zmtOKFPf35rXgO9ceUjpng,334
|
|
386
387
|
airbyte_ops_mcp/mcp/_guidance.py,sha256=48tQSnDnxqXtyGJxxgjz0ZiI814o_7Fj7f6R8jpQ7so,2375
|
|
387
|
-
airbyte_ops_mcp/mcp/_mcp_utils.py,sha256=
|
|
388
|
-
airbyte_ops_mcp/mcp/cloud_connector_versions.py,sha256=
|
|
388
|
+
airbyte_ops_mcp/mcp/_mcp_utils.py,sha256=nhztHcoc-_ASPpJfoDBjxjjqEvQM6_QIrhp7F2UCrQk,11494
|
|
389
|
+
airbyte_ops_mcp/mcp/cloud_connector_versions.py,sha256=XxaS6WBP0sJPRwT7TTPhVH2PzhPqVWMNU5fVdWdxLLk,10361
|
|
389
390
|
airbyte_ops_mcp/mcp/connector_analysis.py,sha256=OC4KrOSkMkKPkOisWnSv96BDDE5TQYHq-Jxa2vtjJpo,298
|
|
390
391
|
airbyte_ops_mcp/mcp/connector_qa.py,sha256=aImpqdnqBPDrz10BS0owsV4kuIU2XdalzgbaGZsbOL0,258
|
|
391
|
-
airbyte_ops_mcp/mcp/github.py,sha256=
|
|
392
|
-
airbyte_ops_mcp/mcp/github_repo_ops.py,sha256=
|
|
393
|
-
airbyte_ops_mcp/mcp/live_tests.py,sha256=
|
|
392
|
+
airbyte_ops_mcp/mcp/github.py,sha256=keB5_LUs6OULXr4Ukg-gzJfeDiC_QvBXSh56yTD9kSQ,7625
|
|
393
|
+
airbyte_ops_mcp/mcp/github_repo_ops.py,sha256=PiERpt8abo20Gz4CfXhrDNlVM4o4FOt5sweZJND2a0s,5314
|
|
394
|
+
airbyte_ops_mcp/mcp/live_tests.py,sha256=eHfgNkEcY1OKzYiJmkxwOluLPiFHIdP4nm_H1H0MXDg,17940
|
|
394
395
|
airbyte_ops_mcp/mcp/metadata.py,sha256=fwGW97WknR5lfKcQnFtK6dU87aA6TmLj1NkKyqDAV9g,270
|
|
395
|
-
airbyte_ops_mcp/mcp/prerelease.py,sha256=
|
|
396
|
-
airbyte_ops_mcp/mcp/prod_db_queries.py,sha256=
|
|
397
|
-
airbyte_ops_mcp/mcp/prompts.py,sha256=
|
|
396
|
+
airbyte_ops_mcp/mcp/prerelease.py,sha256=RTEq9uguaREeZ9BqC2In3abdGuqg2IUZZ43KNoYj66Y,9444
|
|
397
|
+
airbyte_ops_mcp/mcp/prod_db_queries.py,sha256=RV909zZX156WKXzwtu68Sd071tmaiJaCJz9wNHg3j0I,12399
|
|
398
|
+
airbyte_ops_mcp/mcp/prompts.py,sha256=mJld9mdPECXYZffWXGSvNs4Xevx3rxqUGNlzGKVC2_s,1599
|
|
398
399
|
airbyte_ops_mcp/mcp/registry.py,sha256=PW-VYUj42qx2pQ_apUkVaoUFq7VgB9zEU7-aGrkSCCw,290
|
|
399
400
|
airbyte_ops_mcp/mcp/server.py,sha256=7zi91xioVTx1q-bEleekZH2c2JnbzDQt_6zxdEwnLbg,2958
|
|
400
|
-
airbyte_ops_mcp/mcp/server_info.py,sha256=
|
|
401
|
+
airbyte_ops_mcp/mcp/server_info.py,sha256=Yi4B1auW64QZGBDas5mro_vwTjvrP785TFNSBP7GhRg,2361
|
|
401
402
|
airbyte_ops_mcp/prod_db_access/__init__.py,sha256=5pxouMPY1beyWlB0UwPnbaLTKTHqU6X82rbbgKY2vYU,1069
|
|
402
403
|
airbyte_ops_mcp/prod_db_access/db_engine.py,sha256=ia1KcuQOXi3Qhy_MnxYmccCBJ4rAt_d4nVDjcyzje6o,4289
|
|
403
404
|
airbyte_ops_mcp/prod_db_access/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
404
|
-
airbyte_ops_mcp/prod_db_access/queries.py,sha256=
|
|
405
|
+
airbyte_ops_mcp/prod_db_access/queries.py,sha256=jUGJErgDmaKf7mswgVEGin8bjsUFAxegSAOB47XzT9k,8724
|
|
405
406
|
airbyte_ops_mcp/prod_db_access/sql.py,sha256=zHPucNuMlfxz3aU8vYo1ziiGk0lIncG9XmblEoRDd4c,12725
|
|
406
407
|
airbyte_ops_mcp/registry/__init__.py,sha256=iEaPlt9GrnlaLbc__98TguNeZG8wuQu7S-_2QkhHcbA,858
|
|
407
408
|
airbyte_ops_mcp/registry/models.py,sha256=B4L4TKr52wo0xs0CqvCBrpowqjShzVnZ5eTr2-EyhNs,2346
|
|
408
409
|
airbyte_ops_mcp/registry/publish.py,sha256=VoPxsM2_0zJ829orzCRN-kjgcJtuBNyXgW4I9J680ro,12717
|
|
409
|
-
airbyte_internal_ops-0.1.
|
|
410
|
-
airbyte_internal_ops-0.1.
|
|
411
|
-
airbyte_internal_ops-0.1.
|
|
412
|
-
airbyte_internal_ops-0.1.
|
|
410
|
+
airbyte_internal_ops-0.1.8.dist-info/METADATA,sha256=BSNP1wsy5PbIFtyq6RbvCGPHgY3C8vC5UNsAzjqZAOk,5282
|
|
411
|
+
airbyte_internal_ops-0.1.8.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
412
|
+
airbyte_internal_ops-0.1.8.dist-info/entry_points.txt,sha256=eUgJ9xIy9PlR-CgRbqRMsh1NVp6jz08v9bul9vCYlU4,111
|
|
413
|
+
airbyte_internal_ops-0.1.8.dist-info/RECORD,,
|
|
@@ -24,6 +24,7 @@ from airbyte_ops_mcp.connection_config_retriever.secrets_resolution import (
|
|
|
24
24
|
get_resolved_config,
|
|
25
25
|
)
|
|
26
26
|
from airbyte_ops_mcp.constants import CLOUD_REGISTRY_URL, ConnectionObject
|
|
27
|
+
from airbyte_ops_mcp.gcp_auth import get_secret_manager_client
|
|
27
28
|
from airbyte_ops_mcp.prod_db_access.db_engine import get_pool
|
|
28
29
|
|
|
29
30
|
LOGGER = logging.getLogger(__name__)
|
|
@@ -306,7 +307,7 @@ def retrieve_objects(
|
|
|
306
307
|
"""
|
|
307
308
|
connection_candidates = [TestingCandidate(connection_id=connection_id)]
|
|
308
309
|
|
|
309
|
-
secret_manager_client =
|
|
310
|
+
secret_manager_client = get_secret_manager_client()
|
|
310
311
|
connection_pool = get_pool(secret_manager_client)
|
|
311
312
|
|
|
312
313
|
with connection_pool.connect() as db_conn:
|
airbyte_ops_mcp/constants.py
CHANGED
|
@@ -12,6 +12,10 @@ MCP_SERVER_NAME = "airbyte-internal-ops"
|
|
|
12
12
|
ENV_AIRBYTE_INTERNAL_ADMIN_FLAG = "AIRBYTE_INTERNAL_ADMIN_FLAG"
|
|
13
13
|
ENV_AIRBYTE_INTERNAL_ADMIN_USER = "AIRBYTE_INTERNAL_ADMIN_USER"
|
|
14
14
|
|
|
15
|
+
# Environment variable for GCP credentials (JSON content, not file path)
|
|
16
|
+
ENV_GCP_PROD_DB_ACCESS_CREDENTIALS = "GCP_PROD_DB_ACCESS_CREDENTIALS"
|
|
17
|
+
"""Environment variable containing GCP service account JSON credentials for prod DB access."""
|
|
18
|
+
|
|
15
19
|
# Expected values for internal admin authentication
|
|
16
20
|
EXPECTED_ADMIN_FLAG_VALUE = "airbyte.io"
|
|
17
21
|
EXPECTED_ADMIN_EMAIL_DOMAIN = "@airbyte.io"
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
+
"""Centralized GCP authentication utilities.
|
|
3
|
+
|
|
4
|
+
This module provides a single code path for GCP credential handling across
|
|
5
|
+
the airbyte-ops-mcp codebase. It supports both standard Application Default
|
|
6
|
+
Credentials (ADC) and the GCP_PROD_DB_ACCESS_CREDENTIALS environment variable
|
|
7
|
+
used internally at Airbyte.
|
|
8
|
+
|
|
9
|
+
Usage:
|
|
10
|
+
from airbyte_ops_mcp.gcp_auth import get_secret_manager_client
|
|
11
|
+
|
|
12
|
+
# Get a properly authenticated Secret Manager client
|
|
13
|
+
client = get_secret_manager_client()
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import logging
|
|
19
|
+
import os
|
|
20
|
+
import tempfile
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
|
|
23
|
+
from google.cloud import secretmanager
|
|
24
|
+
|
|
25
|
+
from airbyte_ops_mcp.constants import ENV_GCP_PROD_DB_ACCESS_CREDENTIALS
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger(__name__)
|
|
28
|
+
|
|
29
|
+
# Environment variable name (internal to GCP libraries)
|
|
30
|
+
ENV_GOOGLE_APPLICATION_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"
|
|
31
|
+
|
|
32
|
+
# Module-level cache for the credentials file path
|
|
33
|
+
_credentials_file_path: str | None = None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def ensure_adc_credentials() -> str | None:
|
|
37
|
+
"""Ensure GCP Application Default Credentials are available.
|
|
38
|
+
|
|
39
|
+
If GOOGLE_APPLICATION_CREDENTIALS is not set but GCP_PROD_DB_ACCESS_CREDENTIALS is,
|
|
40
|
+
write the JSON credentials to a temp file and set GOOGLE_APPLICATION_CREDENTIALS
|
|
41
|
+
to point to that file. This provides a fallback for internal employees who use
|
|
42
|
+
GCP_PROD_DB_ACCESS_CREDENTIALS as their standard credential source.
|
|
43
|
+
|
|
44
|
+
Note: GOOGLE_APPLICATION_CREDENTIALS must be a file path, not JSON content.
|
|
45
|
+
The GCP_PROD_DB_ACCESS_CREDENTIALS env var contains the JSON content directly,
|
|
46
|
+
so we write it to a temp file first.
|
|
47
|
+
|
|
48
|
+
This function is idempotent and safe to call multiple times.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
The path to the credentials file if one was created, or None if
|
|
52
|
+
GOOGLE_APPLICATION_CREDENTIALS was already set.
|
|
53
|
+
"""
|
|
54
|
+
global _credentials_file_path
|
|
55
|
+
|
|
56
|
+
# If GOOGLE_APPLICATION_CREDENTIALS is already set, nothing to do
|
|
57
|
+
if ENV_GOOGLE_APPLICATION_CREDENTIALS in os.environ:
|
|
58
|
+
return None
|
|
59
|
+
|
|
60
|
+
# Check if we have the fallback credentials
|
|
61
|
+
gsm_creds = os.getenv(ENV_GCP_PROD_DB_ACCESS_CREDENTIALS)
|
|
62
|
+
if not gsm_creds:
|
|
63
|
+
return None
|
|
64
|
+
|
|
65
|
+
# Reuse the same file path if we've already written credentials and file still exists
|
|
66
|
+
if _credentials_file_path is not None and Path(_credentials_file_path).exists():
|
|
67
|
+
os.environ[ENV_GOOGLE_APPLICATION_CREDENTIALS] = _credentials_file_path
|
|
68
|
+
return _credentials_file_path
|
|
69
|
+
|
|
70
|
+
# Write credentials to a temp file
|
|
71
|
+
# Use a unique filename based on PID to avoid collisions between processes
|
|
72
|
+
creds_file = Path(tempfile.gettempdir()) / f"gcp_prod_db_creds_{os.getpid()}.json"
|
|
73
|
+
creds_file.write_text(gsm_creds)
|
|
74
|
+
|
|
75
|
+
# Set restrictive permissions (owner read/write only)
|
|
76
|
+
creds_file.chmod(0o600)
|
|
77
|
+
|
|
78
|
+
_credentials_file_path = str(creds_file)
|
|
79
|
+
os.environ[ENV_GOOGLE_APPLICATION_CREDENTIALS] = _credentials_file_path
|
|
80
|
+
|
|
81
|
+
logger.debug(
|
|
82
|
+
f"Wrote {ENV_GCP_PROD_DB_ACCESS_CREDENTIALS} to {creds_file} and set "
|
|
83
|
+
f"{ENV_GOOGLE_APPLICATION_CREDENTIALS}"
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
return _credentials_file_path
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def get_secret_manager_client() -> secretmanager.SecretManagerServiceClient:
|
|
90
|
+
"""Get a Secret Manager client with proper credential handling.
|
|
91
|
+
|
|
92
|
+
This function ensures GCP credentials are available (supporting the
|
|
93
|
+
GCP_PROD_DB_ACCESS_CREDENTIALS fallback) before creating the client.
|
|
94
|
+
|
|
95
|
+
Returns:
|
|
96
|
+
A configured SecretManagerServiceClient instance.
|
|
97
|
+
"""
|
|
98
|
+
ensure_adc_credentials()
|
|
99
|
+
return secretmanager.SecretManagerServiceClient()
|
|
@@ -32,15 +32,14 @@ from __future__ import annotations
|
|
|
32
32
|
|
|
33
33
|
import logging
|
|
34
34
|
import os
|
|
35
|
-
import tempfile
|
|
36
35
|
from dataclasses import replace
|
|
37
|
-
from pathlib import Path
|
|
38
36
|
from typing import TYPE_CHECKING
|
|
39
37
|
|
|
40
38
|
from airbyte_ops_mcp.connection_config_retriever import (
|
|
41
39
|
ConnectionObject,
|
|
42
40
|
retrieve_objects,
|
|
43
41
|
)
|
|
42
|
+
from airbyte_ops_mcp.gcp_auth import ensure_adc_credentials
|
|
44
43
|
|
|
45
44
|
if TYPE_CHECKING:
|
|
46
45
|
from airbyte_ops_mcp.live_tests.connection_fetcher import ConnectionData
|
|
@@ -50,39 +49,6 @@ logger = logging.getLogger(__name__)
|
|
|
50
49
|
# Environment variable to enable secret retrieval
|
|
51
50
|
ENV_USE_SECRET_RETRIEVER = "USE_CONNECTION_SECRET_RETRIEVER"
|
|
52
51
|
|
|
53
|
-
# GCP credential environment variables
|
|
54
|
-
ENV_GOOGLE_APPLICATION_CREDENTIALS = "GOOGLE_APPLICATION_CREDENTIALS"
|
|
55
|
-
ENV_GCP_PROD_DB_ACCESS_CREDENTIALS = "GCP_PROD_DB_ACCESS_CREDENTIALS"
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def _ensure_gcp_credentials_env() -> None:
|
|
59
|
-
"""Ensure GCP credentials are available via standard env var.
|
|
60
|
-
|
|
61
|
-
If GOOGLE_APPLICATION_CREDENTIALS is not set but GCP_PROD_DB_ACCESS_CREDENTIALS is,
|
|
62
|
-
write the JSON credentials to a temp file and set GOOGLE_APPLICATION_CREDENTIALS
|
|
63
|
-
to point to that file. This provides a fallback for internal employees who use
|
|
64
|
-
GCP_PROD_DB_ACCESS_CREDENTIALS as their standard credential source for prod
|
|
65
|
-
database access.
|
|
66
|
-
|
|
67
|
-
Note: GOOGLE_APPLICATION_CREDENTIALS must be a file path, not JSON content.
|
|
68
|
-
The GCP_PROD_DB_ACCESS_CREDENTIALS env var contains the JSON content directly,
|
|
69
|
-
so we write it to a temp file first.
|
|
70
|
-
|
|
71
|
-
This function is idempotent and safe to call multiple times.
|
|
72
|
-
"""
|
|
73
|
-
if ENV_GOOGLE_APPLICATION_CREDENTIALS not in os.environ:
|
|
74
|
-
gsm_creds = os.getenv(ENV_GCP_PROD_DB_ACCESS_CREDENTIALS)
|
|
75
|
-
if gsm_creds:
|
|
76
|
-
# Write credentials to a temp file since GOOGLE_APPLICATION_CREDENTIALS
|
|
77
|
-
# expects a file path, not JSON content
|
|
78
|
-
creds_file = Path(tempfile.gettempdir()) / "gcp_prod_db_creds.json"
|
|
79
|
-
creds_file.write_text(gsm_creds)
|
|
80
|
-
os.environ[ENV_GOOGLE_APPLICATION_CREDENTIALS] = str(creds_file)
|
|
81
|
-
logger.debug(
|
|
82
|
-
f"Wrote {ENV_GCP_PROD_DB_ACCESS_CREDENTIALS} to {creds_file} and set "
|
|
83
|
-
f"{ENV_GOOGLE_APPLICATION_CREDENTIALS}"
|
|
84
|
-
)
|
|
85
|
-
|
|
86
52
|
|
|
87
53
|
def is_secret_retriever_enabled() -> bool:
|
|
88
54
|
"""Check if secret retrieval is enabled via environment variable.
|
|
@@ -120,7 +86,7 @@ def retrieve_unmasked_config(
|
|
|
120
86
|
The unmasked source config dict, or None if retrieval fails.
|
|
121
87
|
"""
|
|
122
88
|
# Ensure GCP credentials are available (supports GCP_PROD_DB_ACCESS_CREDENTIALS fallback)
|
|
123
|
-
|
|
89
|
+
ensure_adc_credentials()
|
|
124
90
|
|
|
125
91
|
# Only request the source config - that's all we need for secrets
|
|
126
92
|
requested_objects = [ConnectionObject.SOURCE_CONFIG]
|
|
@@ -2,14 +2,17 @@
|
|
|
2
2
|
"""Deferred MCP capability registration for tools, prompts, and resources.
|
|
3
3
|
|
|
4
4
|
This module provides a decorator to tag tool functions with MCP annotations
|
|
5
|
-
for deferred registration.
|
|
5
|
+
for deferred registration. The domain for each tool is automatically derived
|
|
6
|
+
from the file stem of the module where the tool is defined.
|
|
6
7
|
"""
|
|
7
8
|
|
|
8
9
|
from __future__ import annotations
|
|
9
10
|
|
|
11
|
+
import inspect
|
|
10
12
|
from collections.abc import Callable
|
|
11
13
|
from dataclasses import dataclass
|
|
12
14
|
from enum import Enum
|
|
15
|
+
from pathlib import Path
|
|
13
16
|
from typing import Any, TypeVar
|
|
14
17
|
|
|
15
18
|
from fastmcp import FastMCP
|
|
@@ -94,8 +97,39 @@ def should_register_tool(annotations: dict[str, Any]) -> bool:
|
|
|
94
97
|
return True
|
|
95
98
|
|
|
96
99
|
|
|
100
|
+
def _get_caller_file_stem() -> str:
|
|
101
|
+
"""Get the file stem of the caller's module.
|
|
102
|
+
|
|
103
|
+
Walks up the call stack to find the first frame outside this module,
|
|
104
|
+
then returns the stem of that file (e.g., "github" for "github.py").
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
The file stem of the calling module.
|
|
108
|
+
"""
|
|
109
|
+
for frame_info in inspect.stack():
|
|
110
|
+
# Skip frames from this module
|
|
111
|
+
if frame_info.filename != __file__:
|
|
112
|
+
return Path(frame_info.filename).stem
|
|
113
|
+
return "unknown"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def _normalize_domain(domain: str) -> str:
|
|
117
|
+
"""Normalize a domain string to its simple form.
|
|
118
|
+
|
|
119
|
+
Handles both file stems (e.g., "github") and module names
|
|
120
|
+
(e.g., "airbyte_ops_mcp.mcp.github") by extracting the last segment.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
domain: A domain string, either a simple name or a dotted module path.
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
The normalized domain (last segment of a dotted path, or the input if no dots).
|
|
127
|
+
"""
|
|
128
|
+
return domain.rsplit(".", 1)[-1]
|
|
129
|
+
|
|
130
|
+
|
|
97
131
|
def mcp_tool(
|
|
98
|
-
domain: ToolDomain | str,
|
|
132
|
+
domain: ToolDomain | str | None = None,
|
|
99
133
|
*,
|
|
100
134
|
read_only: bool = False,
|
|
101
135
|
destructive: bool = False,
|
|
@@ -108,8 +142,12 @@ def mcp_tool(
|
|
|
108
142
|
This decorator stores the annotations on the function for later use during
|
|
109
143
|
deferred registration. It does not register the tool immediately.
|
|
110
144
|
|
|
145
|
+
The domain is automatically derived from the file stem of the module where
|
|
146
|
+
the tool is defined (e.g., tools in "github.py" get domain "github").
|
|
147
|
+
|
|
111
148
|
Args:
|
|
112
|
-
domain:
|
|
149
|
+
domain: Optional explicit domain override. If not provided, the domain
|
|
150
|
+
is automatically derived from the caller's file stem.
|
|
113
151
|
read_only: If True, tool only reads without making changes (default: False)
|
|
114
152
|
destructive: If True, tool modifies/deletes existing data (default: False)
|
|
115
153
|
idempotent: If True, repeated calls have same effect (default: False)
|
|
@@ -121,11 +159,18 @@ def mcp_tool(
|
|
|
121
159
|
Decorator function that tags the tool with annotations
|
|
122
160
|
|
|
123
161
|
Example:
|
|
124
|
-
@mcp_tool(
|
|
162
|
+
@mcp_tool(read_only=True, idempotent=True)
|
|
125
163
|
def list_connectors_in_repo():
|
|
126
164
|
...
|
|
127
165
|
"""
|
|
128
|
-
|
|
166
|
+
# Auto-derive domain from caller's file stem if not provided
|
|
167
|
+
if domain is None:
|
|
168
|
+
domain_str = _get_caller_file_stem()
|
|
169
|
+
elif isinstance(domain, ToolDomain):
|
|
170
|
+
domain_str = domain.value
|
|
171
|
+
else:
|
|
172
|
+
domain_str = domain
|
|
173
|
+
|
|
129
174
|
annotations: dict[str, Any] = {
|
|
130
175
|
"domain": domain_str,
|
|
131
176
|
READ_ONLY_HINT: read_only,
|
|
@@ -156,7 +201,8 @@ def mcp_prompt(
|
|
|
156
201
|
Args:
|
|
157
202
|
name: Unique name for the prompt
|
|
158
203
|
description: Human-readable description of the prompt
|
|
159
|
-
domain: Optional domain for filtering
|
|
204
|
+
domain: Optional domain for filtering. If not provided, automatically
|
|
205
|
+
derived from the caller's file stem.
|
|
160
206
|
|
|
161
207
|
Returns:
|
|
162
208
|
Decorator function that registers the prompt
|
|
@@ -164,15 +210,20 @@ def mcp_prompt(
|
|
|
164
210
|
Raises:
|
|
165
211
|
ValueError: If a prompt with the same name is already registered
|
|
166
212
|
"""
|
|
213
|
+
# Auto-derive domain from caller's file stem if not provided
|
|
214
|
+
if domain is None:
|
|
215
|
+
domain_str = _get_caller_file_stem()
|
|
216
|
+
elif isinstance(domain, ToolDomain):
|
|
217
|
+
domain_str = domain.value
|
|
218
|
+
else:
|
|
219
|
+
domain_str = domain
|
|
167
220
|
|
|
168
221
|
def decorator(func: Callable[..., list[dict[str, str]]]):
|
|
169
|
-
domain_str = domain.value if isinstance(domain, ToolDomain) else domain
|
|
170
222
|
annotations = {
|
|
171
223
|
"name": name,
|
|
172
224
|
"description": description,
|
|
225
|
+
"domain": domain_str,
|
|
173
226
|
}
|
|
174
|
-
if domain_str is not None:
|
|
175
|
-
annotations["domain"] = domain_str
|
|
176
227
|
_REGISTERED_PROMPTS.append((func, annotations))
|
|
177
228
|
return func
|
|
178
229
|
|
|
@@ -191,7 +242,8 @@ def mcp_resource(
|
|
|
191
242
|
uri: Unique URI for the resource
|
|
192
243
|
description: Human-readable description of the resource
|
|
193
244
|
mime_type: MIME type of the resource content
|
|
194
|
-
domain: Optional domain for filtering
|
|
245
|
+
domain: Optional domain for filtering. If not provided, automatically
|
|
246
|
+
derived from the caller's file stem.
|
|
195
247
|
|
|
196
248
|
Returns:
|
|
197
249
|
Decorator function that registers the resource
|
|
@@ -199,16 +251,21 @@ def mcp_resource(
|
|
|
199
251
|
Raises:
|
|
200
252
|
ValueError: If a resource with the same URI is already registered
|
|
201
253
|
"""
|
|
254
|
+
# Auto-derive domain from caller's file stem if not provided
|
|
255
|
+
if domain is None:
|
|
256
|
+
domain_str = _get_caller_file_stem()
|
|
257
|
+
elif isinstance(domain, ToolDomain):
|
|
258
|
+
domain_str = domain.value
|
|
259
|
+
else:
|
|
260
|
+
domain_str = domain
|
|
202
261
|
|
|
203
262
|
def decorator(func: Callable[..., Any]):
|
|
204
|
-
domain_str = domain.value if isinstance(domain, ToolDomain) else domain
|
|
205
263
|
annotations = {
|
|
206
264
|
"uri": uri,
|
|
207
265
|
"description": description,
|
|
208
266
|
"mime_type": mime_type,
|
|
267
|
+
"domain": domain_str,
|
|
209
268
|
}
|
|
210
|
-
if domain_str is not None:
|
|
211
|
-
annotations["domain"] = domain_str
|
|
212
269
|
_REGISTERED_RESOURCES.append((func, annotations))
|
|
213
270
|
return func
|
|
214
271
|
|
|
@@ -226,11 +283,14 @@ def _register_mcp_callables(
|
|
|
226
283
|
|
|
227
284
|
Args:
|
|
228
285
|
app: The FastMCP app instance
|
|
229
|
-
domain: The domain to register tools for (e.g.,
|
|
286
|
+
domain: The domain to register tools for. Can be a simple name (e.g., "github")
|
|
287
|
+
or a full module path (e.g., "airbyte_ops_mcp.mcp.github" from __name__).
|
|
230
288
|
resource_list: List of (callable, annotations) tuples to register
|
|
231
289
|
register_fn: Function to call for each registration
|
|
232
290
|
"""
|
|
233
291
|
domain_str = domain.value if isinstance(domain, ToolDomain) else domain
|
|
292
|
+
# Normalize to handle both file stems and __name__ module paths
|
|
293
|
+
domain_str = _normalize_domain(domain_str)
|
|
234
294
|
|
|
235
295
|
filtered_callables = [
|
|
236
296
|
(func, ann) for func, ann in resource_list if ann.get("domain") == domain_str
|
|
@@ -242,14 +302,17 @@ def _register_mcp_callables(
|
|
|
242
302
|
|
|
243
303
|
def register_mcp_tools(
|
|
244
304
|
app: FastMCP,
|
|
245
|
-
domain: ToolDomain | str,
|
|
305
|
+
domain: ToolDomain | str | None = None,
|
|
246
306
|
) -> None:
|
|
247
307
|
"""Register tools with the FastMCP app, filtered by domain.
|
|
248
308
|
|
|
249
309
|
Args:
|
|
250
310
|
app: The FastMCP app instance
|
|
251
|
-
domain: The domain to register for
|
|
311
|
+
domain: The domain to register for. If not provided, automatically
|
|
312
|
+
derived from the caller's file stem.
|
|
252
313
|
"""
|
|
314
|
+
if domain is None:
|
|
315
|
+
domain = _get_caller_file_stem()
|
|
253
316
|
|
|
254
317
|
def _register_fn(
|
|
255
318
|
app: FastMCP,
|
|
@@ -271,14 +334,17 @@ def register_mcp_tools(
|
|
|
271
334
|
|
|
272
335
|
def register_mcp_prompts(
|
|
273
336
|
app: FastMCP,
|
|
274
|
-
domain: ToolDomain | str,
|
|
337
|
+
domain: ToolDomain | str | None = None,
|
|
275
338
|
) -> None:
|
|
276
339
|
"""Register prompt callables with the FastMCP app, filtered by domain.
|
|
277
340
|
|
|
278
341
|
Args:
|
|
279
342
|
app: The FastMCP app instance
|
|
280
|
-
domain: The domain to register for
|
|
343
|
+
domain: The domain to register for. If not provided, automatically
|
|
344
|
+
derived from the caller's file stem.
|
|
281
345
|
"""
|
|
346
|
+
if domain is None:
|
|
347
|
+
domain = _get_caller_file_stem()
|
|
282
348
|
|
|
283
349
|
def _register_fn(
|
|
284
350
|
app: FastMCP,
|
|
@@ -300,14 +366,17 @@ def register_mcp_prompts(
|
|
|
300
366
|
|
|
301
367
|
def register_mcp_resources(
|
|
302
368
|
app: FastMCP,
|
|
303
|
-
domain: ToolDomain | str,
|
|
369
|
+
domain: ToolDomain | str | None = None,
|
|
304
370
|
) -> None:
|
|
305
371
|
"""Register resource callables with the FastMCP app, filtered by domain.
|
|
306
372
|
|
|
307
373
|
Args:
|
|
308
374
|
app: The FastMCP app instance
|
|
309
|
-
domain: The domain to register for
|
|
375
|
+
domain: The domain to register for. If not provided, automatically
|
|
376
|
+
derived from the caller's file stem.
|
|
310
377
|
"""
|
|
378
|
+
if domain is None:
|
|
379
|
+
domain = _get_caller_file_stem()
|
|
311
380
|
|
|
312
381
|
def _register_fn(
|
|
313
382
|
app: FastMCP,
|
|
@@ -33,7 +33,7 @@ from airbyte_ops_mcp.cloud_admin.models import (
|
|
|
33
33
|
ConnectorVersionInfo,
|
|
34
34
|
VersionOverrideOperationResult,
|
|
35
35
|
)
|
|
36
|
-
from airbyte_ops_mcp.mcp._mcp_utils import
|
|
36
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
37
37
|
|
|
38
38
|
|
|
39
39
|
def _get_workspace(workspace_id: str) -> CloudWorkspace:
|
|
@@ -63,7 +63,6 @@ def _get_workspace(workspace_id: str) -> CloudWorkspace:
|
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
@mcp_tool(
|
|
66
|
-
ToolDomain.CLOUD_ADMIN,
|
|
67
66
|
read_only=True,
|
|
68
67
|
idempotent=True,
|
|
69
68
|
open_world=True,
|
|
@@ -117,7 +116,6 @@ def get_cloud_connector_version(
|
|
|
117
116
|
|
|
118
117
|
|
|
119
118
|
@mcp_tool(
|
|
120
|
-
ToolDomain.CLOUD_ADMIN,
|
|
121
119
|
destructive=True,
|
|
122
120
|
idempotent=False,
|
|
123
121
|
open_world=True,
|
|
@@ -297,4 +295,4 @@ def register_cloud_connector_version_tools(app: FastMCP) -> None:
|
|
|
297
295
|
Args:
|
|
298
296
|
app: FastMCP application instance
|
|
299
297
|
"""
|
|
300
|
-
register_mcp_tools(app, domain=
|
|
298
|
+
register_mcp_tools(app, domain=__name__)
|
airbyte_ops_mcp/mcp/github.py
CHANGED
|
@@ -15,7 +15,7 @@ import requests
|
|
|
15
15
|
from fastmcp import FastMCP
|
|
16
16
|
from pydantic import BaseModel, Field
|
|
17
17
|
|
|
18
|
-
from airbyte_ops_mcp.mcp._mcp_utils import
|
|
18
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
19
19
|
|
|
20
20
|
GITHUB_API_BASE = "https://api.github.com"
|
|
21
21
|
DOCKERHUB_API_BASE = "https://hub.docker.com/v2"
|
|
@@ -115,7 +115,6 @@ def _get_workflow_run(
|
|
|
115
115
|
|
|
116
116
|
|
|
117
117
|
@mcp_tool(
|
|
118
|
-
ToolDomain.REPO,
|
|
119
118
|
read_only=True,
|
|
120
119
|
idempotent=True,
|
|
121
120
|
open_world=True,
|
|
@@ -222,7 +221,6 @@ def _check_dockerhub_image(
|
|
|
222
221
|
|
|
223
222
|
|
|
224
223
|
@mcp_tool(
|
|
225
|
-
ToolDomain.REPO,
|
|
226
224
|
read_only=True,
|
|
227
225
|
idempotent=True,
|
|
228
226
|
open_world=True,
|
|
@@ -276,4 +274,4 @@ def register_github_tools(app: FastMCP) -> None:
|
|
|
276
274
|
Args:
|
|
277
275
|
app: FastMCP application instance
|
|
278
276
|
"""
|
|
279
|
-
register_mcp_tools(app, domain=
|
|
277
|
+
register_mcp_tools(app, domain=__name__)
|
|
@@ -15,7 +15,7 @@ from pydantic import BaseModel
|
|
|
15
15
|
from airbyte_ops_mcp.airbyte_repo.bump_version import bump_connector_version
|
|
16
16
|
from airbyte_ops_mcp.airbyte_repo.list_connectors import list_connectors
|
|
17
17
|
from airbyte_ops_mcp.airbyte_repo.utils import resolve_diff_range
|
|
18
|
-
from airbyte_ops_mcp.mcp._mcp_utils import
|
|
18
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class ConnectorListResponse(BaseModel):
|
|
@@ -36,7 +36,6 @@ class BumpVersionResponse(BaseModel):
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
@mcp_tool(
|
|
39
|
-
ToolDomain.REPO,
|
|
40
39
|
read_only=True,
|
|
41
40
|
idempotent=True,
|
|
42
41
|
open_world=False,
|
|
@@ -102,7 +101,6 @@ def list_connectors_in_repo(
|
|
|
102
101
|
|
|
103
102
|
|
|
104
103
|
@mcp_tool(
|
|
105
|
-
ToolDomain.REPO,
|
|
106
104
|
read_only=False,
|
|
107
105
|
idempotent=False,
|
|
108
106
|
open_world=False,
|
|
@@ -164,4 +162,4 @@ def register_github_repo_ops_tools(app: FastMCP) -> None:
|
|
|
164
162
|
Args:
|
|
165
163
|
app: FastMCP application instance
|
|
166
164
|
"""
|
|
167
|
-
register_mcp_tools(app, domain=
|
|
165
|
+
register_mcp_tools(app, domain=__name__)
|
|
@@ -21,7 +21,7 @@ from airbyte.cloud.auth import resolve_cloud_client_id, resolve_cloud_client_sec
|
|
|
21
21
|
from fastmcp import FastMCP
|
|
22
22
|
from pydantic import BaseModel, Field
|
|
23
23
|
|
|
24
|
-
from airbyte_ops_mcp.mcp._mcp_utils import
|
|
24
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
25
25
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
27
|
|
|
@@ -308,7 +308,6 @@ class RunLiveConnectionTestsResponse(BaseModel):
|
|
|
308
308
|
|
|
309
309
|
|
|
310
310
|
@mcp_tool(
|
|
311
|
-
ToolDomain.LIVE_TESTS,
|
|
312
311
|
read_only=False,
|
|
313
312
|
idempotent=False,
|
|
314
313
|
open_world=True,
|
|
@@ -512,4 +511,4 @@ def register_live_tests_tools(app: FastMCP) -> None:
|
|
|
512
511
|
Args:
|
|
513
512
|
app: FastMCP application instance
|
|
514
513
|
"""
|
|
515
|
-
register_mcp_tools(app, domain=
|
|
514
|
+
register_mcp_tools(app, domain=__name__)
|
|
@@ -16,7 +16,7 @@ import yaml
|
|
|
16
16
|
from fastmcp import FastMCP
|
|
17
17
|
from pydantic import BaseModel, Field
|
|
18
18
|
|
|
19
|
-
from airbyte_ops_mcp.mcp._mcp_utils import
|
|
19
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
20
20
|
|
|
21
21
|
GITHUB_API_BASE = "https://api.github.com"
|
|
22
22
|
DEFAULT_REPO_OWNER = "airbytehq"
|
|
@@ -205,7 +205,6 @@ def _trigger_workflow_dispatch(
|
|
|
205
205
|
|
|
206
206
|
|
|
207
207
|
@mcp_tool(
|
|
208
|
-
ToolDomain.REPO,
|
|
209
208
|
read_only=False,
|
|
210
209
|
destructive=False,
|
|
211
210
|
idempotent=False,
|
|
@@ -310,4 +309,4 @@ def register_prerelease_tools(app: FastMCP) -> None:
|
|
|
310
309
|
Args:
|
|
311
310
|
app: FastMCP application instance
|
|
312
311
|
"""
|
|
313
|
-
register_mcp_tools(app, domain=
|
|
312
|
+
register_mcp_tools(app, domain=__name__)
|
|
@@ -14,7 +14,7 @@ from airbyte.exceptions import PyAirbyteInputError
|
|
|
14
14
|
from fastmcp import FastMCP
|
|
15
15
|
from pydantic import Field
|
|
16
16
|
|
|
17
|
-
from airbyte_ops_mcp.mcp._mcp_utils import
|
|
17
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
18
18
|
from airbyte_ops_mcp.prod_db_access.queries import (
|
|
19
19
|
query_actors_pinned_to_version,
|
|
20
20
|
query_connections_by_connector,
|
|
@@ -86,7 +86,6 @@ def _resolve_canonical_name_to_definition_id(canonical_name: str) -> str:
|
|
|
86
86
|
|
|
87
87
|
|
|
88
88
|
@mcp_tool(
|
|
89
|
-
ToolDomain.CLOUD_ADMIN,
|
|
90
89
|
read_only=True,
|
|
91
90
|
idempotent=True,
|
|
92
91
|
)
|
|
@@ -104,7 +103,6 @@ def query_prod_dataplanes() -> list[dict[str, Any]]:
|
|
|
104
103
|
|
|
105
104
|
|
|
106
105
|
@mcp_tool(
|
|
107
|
-
ToolDomain.CLOUD_ADMIN,
|
|
108
106
|
read_only=True,
|
|
109
107
|
idempotent=True,
|
|
110
108
|
)
|
|
@@ -128,7 +126,6 @@ def query_prod_workspace_info(
|
|
|
128
126
|
|
|
129
127
|
|
|
130
128
|
@mcp_tool(
|
|
131
|
-
ToolDomain.CLOUD_ADMIN,
|
|
132
129
|
read_only=True,
|
|
133
130
|
idempotent=True,
|
|
134
131
|
)
|
|
@@ -151,7 +148,6 @@ def query_prod_connector_versions(
|
|
|
151
148
|
|
|
152
149
|
|
|
153
150
|
@mcp_tool(
|
|
154
|
-
ToolDomain.CLOUD_ADMIN,
|
|
155
151
|
read_only=True,
|
|
156
152
|
idempotent=True,
|
|
157
153
|
)
|
|
@@ -179,7 +175,6 @@ def query_prod_new_connector_releases(
|
|
|
179
175
|
|
|
180
176
|
|
|
181
177
|
@mcp_tool(
|
|
182
|
-
ToolDomain.CLOUD_ADMIN,
|
|
183
178
|
read_only=True,
|
|
184
179
|
idempotent=True,
|
|
185
180
|
)
|
|
@@ -205,7 +200,6 @@ def query_prod_actors_by_connector_version(
|
|
|
205
200
|
|
|
206
201
|
|
|
207
202
|
@mcp_tool(
|
|
208
|
-
ToolDomain.CLOUD_ADMIN,
|
|
209
203
|
read_only=True,
|
|
210
204
|
idempotent=True,
|
|
211
205
|
)
|
|
@@ -252,7 +246,6 @@ def query_prod_connector_version_sync_results(
|
|
|
252
246
|
|
|
253
247
|
|
|
254
248
|
@mcp_tool(
|
|
255
|
-
ToolDomain.CLOUD_ADMIN,
|
|
256
249
|
read_only=True,
|
|
257
250
|
idempotent=True,
|
|
258
251
|
open_world=True,
|
|
@@ -354,4 +347,4 @@ def query_prod_connections_by_connector(
|
|
|
354
347
|
|
|
355
348
|
def register_prod_db_query_tools(app: FastMCP) -> None:
|
|
356
349
|
"""Register prod DB query tools with the FastMCP app."""
|
|
357
|
-
register_mcp_tools(app,
|
|
350
|
+
register_mcp_tools(app, domain=__name__)
|
airbyte_ops_mcp/mcp/prompts.py
CHANGED
|
@@ -13,13 +13,12 @@ from fastmcp import FastMCP
|
|
|
13
13
|
from pydantic import Field
|
|
14
14
|
|
|
15
15
|
from airbyte_ops_mcp.mcp._guidance import TEST_MY_TOOLS_GUIDANCE
|
|
16
|
-
from airbyte_ops_mcp.mcp._mcp_utils import
|
|
16
|
+
from airbyte_ops_mcp.mcp._mcp_utils import mcp_prompt, register_mcp_prompts
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@mcp_prompt(
|
|
20
20
|
name="test-my-tools",
|
|
21
21
|
description="Test all available MCP tools to confirm they are working properly",
|
|
22
|
-
domain=ToolDomain.PROMPTS,
|
|
23
22
|
)
|
|
24
23
|
def test_my_tools_prompt(
|
|
25
24
|
scope: Annotated[
|
|
@@ -57,4 +56,4 @@ def register_prompts(app: FastMCP) -> None:
|
|
|
57
56
|
Args:
|
|
58
57
|
app: FastMCP application instance
|
|
59
58
|
"""
|
|
60
|
-
register_mcp_prompts(app,
|
|
59
|
+
register_mcp_prompts(app, domain=__name__)
|
|
@@ -12,7 +12,6 @@ from fastmcp import FastMCP
|
|
|
12
12
|
|
|
13
13
|
from airbyte_ops_mcp.constants import MCP_SERVER_NAME
|
|
14
14
|
from airbyte_ops_mcp.mcp._mcp_utils import (
|
|
15
|
-
ToolDomain,
|
|
16
15
|
mcp_resource,
|
|
17
16
|
register_mcp_resources,
|
|
18
17
|
)
|
|
@@ -64,7 +63,6 @@ def _get_version_info() -> dict[str, str | list[str] | None]:
|
|
|
64
63
|
uri=f"{MCP_SERVER_NAME}://server/info",
|
|
65
64
|
description="Server information for the Airbyte Admin MCP server",
|
|
66
65
|
mime_type="application/json",
|
|
67
|
-
domain=ToolDomain.SERVER_INFO,
|
|
68
66
|
)
|
|
69
67
|
def mcp_server_info() -> dict[str, str | list[str] | None]:
|
|
70
68
|
"""Resource that returns information for the MCP server.
|
|
@@ -83,4 +81,4 @@ def register_server_info_resources(app: FastMCP) -> None:
|
|
|
83
81
|
Args:
|
|
84
82
|
app: FastMCP application instance
|
|
85
83
|
"""
|
|
86
|
-
register_mcp_resources(app, domain=
|
|
84
|
+
register_mcp_resources(app, domain=__name__)
|
|
@@ -16,6 +16,7 @@ from typing import Any
|
|
|
16
16
|
import sqlalchemy
|
|
17
17
|
from google.cloud import secretmanager
|
|
18
18
|
|
|
19
|
+
from airbyte_ops_mcp.gcp_auth import get_secret_manager_client
|
|
19
20
|
from airbyte_ops_mcp.prod_db_access.db_engine import get_pool
|
|
20
21
|
from airbyte_ops_mcp.prod_db_access.sql import (
|
|
21
22
|
SELECT_ACTORS_PINNED_TO_VERSION,
|
|
@@ -52,7 +53,7 @@ def _run_sql_query(
|
|
|
52
53
|
List of row dicts from the query result
|
|
53
54
|
"""
|
|
54
55
|
if gsm_client is None:
|
|
55
|
-
gsm_client =
|
|
56
|
+
gsm_client = get_secret_manager_client()
|
|
56
57
|
pool = get_pool(gsm_client)
|
|
57
58
|
start = perf_counter()
|
|
58
59
|
with pool.connect() as conn:
|
|
File without changes
|
{airbyte_internal_ops-0.1.7.dist-info → airbyte_internal_ops-0.1.8.dist-info}/entry_points.txt
RENAMED
|
File without changes
|