airbyte-internal-ops 0.5.1__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {airbyte_internal_ops-0.5.1.dist-info → airbyte_internal_ops-0.6.0.dist-info}/METADATA +2 -1
- {airbyte_internal_ops-0.5.1.dist-info → airbyte_internal_ops-0.6.0.dist-info}/RECORD +18 -22
- airbyte_ops_mcp/cli/cloud.py +8 -6
- airbyte_ops_mcp/constants.py +13 -0
- airbyte_ops_mcp/mcp/__init__.py +9 -6
- airbyte_ops_mcp/mcp/cloud_connector_versions.py +28 -22
- airbyte_ops_mcp/mcp/gcp_logs.py +1 -1
- airbyte_ops_mcp/mcp/github_actions.py +2 -2
- airbyte_ops_mcp/mcp/github_repo_ops.py +2 -2
- airbyte_ops_mcp/mcp/prerelease.py +2 -2
- airbyte_ops_mcp/mcp/prod_db_queries.py +2 -2
- airbyte_ops_mcp/mcp/prompts.py +2 -2
- airbyte_ops_mcp/mcp/regression_tests.py +2 -2
- airbyte_ops_mcp/mcp/server.py +57 -5
- airbyte_ops_mcp/regression_tests/__init__.py +2 -0
- airbyte_ops_mcp/regression_tests/connection_secret_retriever.py +25 -5
- airbyte_ops_mcp/_annotations.py +0 -51
- airbyte_ops_mcp/mcp/_http_headers.py +0 -254
- airbyte_ops_mcp/mcp/_mcp_utils.py +0 -398
- airbyte_ops_mcp/mcp/server_info.py +0 -84
- {airbyte_internal_ops-0.5.1.dist-info → airbyte_internal_ops-0.6.0.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.5.1.dist-info → airbyte_internal_ops-0.6.0.dist-info}/entry_points.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: airbyte-internal-ops
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: MCP and API interfaces that let the agents do the admin work
|
|
5
5
|
Author-email: Aaron Steers <aj@airbyte.io>
|
|
6
6
|
Keywords: admin,airbyte,api,mcp
|
|
@@ -21,6 +21,7 @@ Requires-Dist: cloud-sql-python-connector[pg8000]<2.0,>=1.7.0
|
|
|
21
21
|
Requires-Dist: cyclopts<5.0,>=4.0.0
|
|
22
22
|
Requires-Dist: docker<7.0,>=6.0
|
|
23
23
|
Requires-Dist: dpath<3.0,>=2.1.5
|
|
24
|
+
Requires-Dist: fastmcp-extensions<1.0,>=0.2.0
|
|
24
25
|
Requires-Dist: fastmcp<3.0,>=2.12.1
|
|
25
26
|
Requires-Dist: gitpython<4.0,>=3.1.29
|
|
26
27
|
Requires-Dist: google-cloud-logging<4.0,>=3.9.0
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
airbyte_ops_mcp/__init__.py,sha256=tuzdlMkfnWBnsri5KGHM2M_xuNnzFk2u_aR79mmN7Yg,772
|
|
2
|
-
airbyte_ops_mcp/
|
|
3
|
-
airbyte_ops_mcp/constants.py,sha256=lhifXJggCKYOjJAprTQ8N-kXrScTBVWuo05CJTQdVnM,7355
|
|
2
|
+
airbyte_ops_mcp/constants.py,sha256=xU8ARMs7jG0-1hseKn3K1dmhyMOMM3JTetRZugLSKEo,7695
|
|
4
3
|
airbyte_ops_mcp/docker_hub.py,sha256=qdOYpj2KOFOsEGsl2b2rcVPzyYDharOVM_lJxNTytds,5833
|
|
5
4
|
airbyte_ops_mcp/gcp_auth.py,sha256=i0cm1_xX4fj_31iKlfARpNvTaSr85iGTSw9KMf4f4MU,7206
|
|
6
5
|
airbyte_ops_mcp/github_actions.py,sha256=FSi_tjS9TbwRVp8dwlDZhFOi7lJXEZQLhPm2KpcjNlY,7022
|
|
@@ -245,7 +244,7 @@ airbyte_ops_mcp/cli/__init__.py,sha256=XpL7FyVfgabfBF2JR7u7NwJ2krlYqjd_OwLcWf-Xc
|
|
|
245
244
|
airbyte_ops_mcp/cli/_base.py,sha256=I8tWnyQf0ks4r3J8N8h-5GZxyn37T-55KsbuHnxYlcg,415
|
|
246
245
|
airbyte_ops_mcp/cli/_shared.py,sha256=jg-xMyGzTCGPqKd8VTfE_3kGPIyO_3Kx5sQbG4rPc0Y,1311
|
|
247
246
|
airbyte_ops_mcp/cli/app.py,sha256=SEdBpqFUG2O8zGV5ifwptxrLGFph_dLr66-MX9d69gQ,789
|
|
248
|
-
airbyte_ops_mcp/cli/cloud.py,sha256=
|
|
247
|
+
airbyte_ops_mcp/cli/cloud.py,sha256=jac0FcG3UvNNeNNhqZR04uY9BCyVvins1kjO0LRZ86Y,45861
|
|
249
248
|
airbyte_ops_mcp/cli/gh.py,sha256=koJPu0MDB6AW7mJq2z4dZV65ofvsZTkqoeitGF8KJR8,5364
|
|
250
249
|
airbyte_ops_mcp/cli/registry.py,sha256=L4nDKhlegr31gSE-GUvDFSq10KgDz5kJuZXgLIxYIyg,9785
|
|
251
250
|
airbyte_ops_mcp/cli/repo.py,sha256=G1hoQpH0XYhUH3FFOsia9xabGB0LP9o3XcwBuqvFVo0,16331
|
|
@@ -260,24 +259,21 @@ airbyte_ops_mcp/connection_config_retriever/retrieval.py,sha256=s6yeCyrboWkUd6Kd
|
|
|
260
259
|
airbyte_ops_mcp/connection_config_retriever/secrets_resolution.py,sha256=12g0lZzhCzAPl4Iv4eMW6d76mvXjIBGspOnNhywzks4,3644
|
|
261
260
|
airbyte_ops_mcp/gcp_logs/__init__.py,sha256=IqkxclXJnD1U4L2at7aC9GYqPXnuLdYLgmkm3ZiIu6s,409
|
|
262
261
|
airbyte_ops_mcp/gcp_logs/error_lookup.py,sha256=Ufl1FtNQJKP_yWndVT1Xku1mT-gxW_0atmNMCYMXvOo,12757
|
|
263
|
-
airbyte_ops_mcp/mcp/__init__.py,sha256=
|
|
262
|
+
airbyte_ops_mcp/mcp/__init__.py,sha256=Y5K-iKUxSY5KM_2XWrYRJqGpjjTHE_ezriED98ZzalU,538
|
|
264
263
|
airbyte_ops_mcp/mcp/_guidance.py,sha256=48tQSnDnxqXtyGJxxgjz0ZiI814o_7Fj7f6R8jpQ7so,2375
|
|
265
|
-
airbyte_ops_mcp/mcp/
|
|
266
|
-
airbyte_ops_mcp/mcp/_mcp_utils.py,sha256=WNwcGzF7XGKZNAYRt0Uhj5BkRfmwqnFABCrk77OZjRw,11512
|
|
267
|
-
airbyte_ops_mcp/mcp/cloud_connector_versions.py,sha256=XXsXtBrNN9lbwzZQvKkIjmMnzBSZPSaupQSnd99pXhA,34272
|
|
264
|
+
airbyte_ops_mcp/mcp/cloud_connector_versions.py,sha256=boafO4ipwdnHZCuN8jxNw5xNmwgH19iBHWQv2NxCujk,34470
|
|
268
265
|
airbyte_ops_mcp/mcp/connector_analysis.py,sha256=OC4KrOSkMkKPkOisWnSv96BDDE5TQYHq-Jxa2vtjJpo,298
|
|
269
266
|
airbyte_ops_mcp/mcp/connector_qa.py,sha256=aImpqdnqBPDrz10BS0owsV4kuIU2XdalzgbaGZsbOL0,258
|
|
270
|
-
airbyte_ops_mcp/mcp/gcp_logs.py,sha256=
|
|
271
|
-
airbyte_ops_mcp/mcp/github_actions.py,sha256=
|
|
272
|
-
airbyte_ops_mcp/mcp/github_repo_ops.py,sha256=
|
|
267
|
+
airbyte_ops_mcp/mcp/gcp_logs.py,sha256=QCDQHmsxQHJ26BB0sxkBgKXr7Ja9wVFkdpY6423H-xo,2677
|
|
268
|
+
airbyte_ops_mcp/mcp/github_actions.py,sha256=G0NmjNVWpVtLXdQnhX7qJwPFkfEX5gBXf55xt0FpJJ0,11752
|
|
269
|
+
airbyte_ops_mcp/mcp/github_repo_ops.py,sha256=YHeuN7Xc_L3xkJ-F3l1t1TIPW2j2CjecBDbb0DUvZO8,5306
|
|
273
270
|
airbyte_ops_mcp/mcp/metadata.py,sha256=fwGW97WknR5lfKcQnFtK6dU87aA6TmLj1NkKyqDAV9g,270
|
|
274
|
-
airbyte_ops_mcp/mcp/prerelease.py,sha256=
|
|
275
|
-
airbyte_ops_mcp/mcp/prod_db_queries.py,sha256=
|
|
276
|
-
airbyte_ops_mcp/mcp/prompts.py,sha256=
|
|
271
|
+
airbyte_ops_mcp/mcp/prerelease.py,sha256=jSrAwk95vZLfwYFn5Menb-ziAMTUZnzAMHIJAitd9x8,9553
|
|
272
|
+
airbyte_ops_mcp/mcp/prod_db_queries.py,sha256=EdZyrccrM5tHKEtjks4598hzbZ62FuDCCHTjjFT4Wt0,47545
|
|
273
|
+
airbyte_ops_mcp/mcp/prompts.py,sha256=v4bguskw7hSsISkseACzKQm5QwrIXmiwbs27oclXTE8,1591
|
|
277
274
|
airbyte_ops_mcp/mcp/registry.py,sha256=PW-VYUj42qx2pQ_apUkVaoUFq7VgB9zEU7-aGrkSCCw,290
|
|
278
|
-
airbyte_ops_mcp/mcp/regression_tests.py,sha256=
|
|
279
|
-
airbyte_ops_mcp/mcp/server.py,sha256=
|
|
280
|
-
airbyte_ops_mcp/mcp/server_info.py,sha256=Yi4B1auW64QZGBDas5mro_vwTjvrP785TFNSBP7GhRg,2361
|
|
275
|
+
airbyte_ops_mcp/mcp/regression_tests.py,sha256=zwdQ-ymUhWtVcIjwiNIZAC151GKhuxi55HDi4S91RnI,17046
|
|
276
|
+
airbyte_ops_mcp/mcp/server.py,sha256=u9P-cJkAe9KkSj6Kcvkly68toOn1Wt8KCOxLrmav2u0,7061
|
|
281
277
|
airbyte_ops_mcp/prod_db_access/__init__.py,sha256=5pxouMPY1beyWlB0UwPnbaLTKTHqU6X82rbbgKY2vYU,1069
|
|
282
278
|
airbyte_ops_mcp/prod_db_access/db_engine.py,sha256=VUqEWZtharJUR-Cri_pMwtGh1C4Neu4s195mbEXlm-w,9190
|
|
283
279
|
airbyte_ops_mcp/prod_db_access/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -286,12 +282,12 @@ airbyte_ops_mcp/prod_db_access/sql.py,sha256=E7HQratZC5LbCXdvpQzZRd09SmCVnY_4Ru-
|
|
|
286
282
|
airbyte_ops_mcp/registry/__init__.py,sha256=iEaPlt9GrnlaLbc__98TguNeZG8wuQu7S-_2QkhHcbA,858
|
|
287
283
|
airbyte_ops_mcp/registry/models.py,sha256=B4L4TKr52wo0xs0CqvCBrpowqjShzVnZ5eTr2-EyhNs,2346
|
|
288
284
|
airbyte_ops_mcp/registry/publish.py,sha256=VoPxsM2_0zJ829orzCRN-kjgcJtuBNyXgW4I9J680ro,12717
|
|
289
|
-
airbyte_ops_mcp/regression_tests/__init__.py,sha256=
|
|
285
|
+
airbyte_ops_mcp/regression_tests/__init__.py,sha256=qdveN866w50VmiDmTD0IW8Buo4UUn_1m9mKNS6hgj5U,1092
|
|
290
286
|
airbyte_ops_mcp/regression_tests/cdk_secrets.py,sha256=iRjqqBS96KZoswfgT7ju-pE_pfbYoDy4PfrK-K8uyYs,3204
|
|
291
287
|
airbyte_ops_mcp/regression_tests/ci_output.py,sha256=os69gcEhbomrGRAfnwPYAXhsAiJSWOuyW1gWZPx9-hw,15498
|
|
292
288
|
airbyte_ops_mcp/regression_tests/config.py,sha256=dwWeY0tatdbwl9BqbhZ7EljoZDCtKmGO5fvOAIxeXmA,5873
|
|
293
289
|
airbyte_ops_mcp/regression_tests/connection_fetcher.py,sha256=laGkxVfDl8_S-Azst28wijvPnOB-bmuuk2B_2e-0yjY,13170
|
|
294
|
-
airbyte_ops_mcp/regression_tests/connection_secret_retriever.py,sha256=
|
|
290
|
+
airbyte_ops_mcp/regression_tests/connection_secret_retriever.py,sha256=e2RvLEtAcpSHnYobP3wGNYGk8VNVI6MallX3cS0UPgA,5561
|
|
295
291
|
airbyte_ops_mcp/regression_tests/connector_runner.py,sha256=OZzUa2aLh0sHaEARsDePOA-e3qEX4cvh3Jhnvi8S1rY,10130
|
|
296
292
|
airbyte_ops_mcp/regression_tests/evaluation_modes.py,sha256=lAL6pEDmy_XCC7_m4_NXjt_f6Z8CXeAhMkc0FU8bm_M,1364
|
|
297
293
|
airbyte_ops_mcp/regression_tests/http_metrics.py,sha256=busCYLb7qID0vv7yAtzARf__-HukpE3mAoc-pYI_gCQ,13256
|
|
@@ -306,7 +302,7 @@ airbyte_ops_mcp/regression_tests/regression/comparators.py,sha256=MJkLZEKHivgrG0
|
|
|
306
302
|
airbyte_ops_mcp/regression_tests/validation/__init__.py,sha256=MBEwGOoNuqT4_oCahtoK62OKWIjUCfWa7vZTxNj_0Ek,1532
|
|
307
303
|
airbyte_ops_mcp/regression_tests/validation/catalog_validators.py,sha256=jqqVAMOk0mtdPgwu4d0hA0ZEjtsNh5gapvGydRv3_qk,12553
|
|
308
304
|
airbyte_ops_mcp/regression_tests/validation/record_validators.py,sha256=RjauAhKWNwxMBTu0eNS2hMFNQVs5CLbQU51kp6FOVDk,7432
|
|
309
|
-
airbyte_internal_ops-0.
|
|
310
|
-
airbyte_internal_ops-0.
|
|
311
|
-
airbyte_internal_ops-0.
|
|
312
|
-
airbyte_internal_ops-0.
|
|
305
|
+
airbyte_internal_ops-0.6.0.dist-info/METADATA,sha256=dalsaOUv8ws3bbG5Y8AdwrwarYviB2C33gFQMwZ3pyA,5777
|
|
306
|
+
airbyte_internal_ops-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
307
|
+
airbyte_internal_ops-0.6.0.dist-info/entry_points.txt,sha256=WxP0l7bRFss4Cr5uQqVj9mTEKwnRKouNuphXQF0lotA,171
|
|
308
|
+
airbyte_internal_ops-0.6.0.dist-info/RECORD,,
|
airbyte_ops_mcp/cli/cloud.py
CHANGED
|
@@ -65,6 +65,7 @@ from airbyte_ops_mcp.regression_tests.connection_fetcher import (
|
|
|
65
65
|
save_connection_data_to_files,
|
|
66
66
|
)
|
|
67
67
|
from airbyte_ops_mcp.regression_tests.connection_secret_retriever import (
|
|
68
|
+
SecretRetrievalError,
|
|
68
69
|
enrich_config_with_secrets,
|
|
69
70
|
should_use_secret_retriever,
|
|
70
71
|
)
|
|
@@ -847,13 +848,14 @@ def regression_test(
|
|
|
847
848
|
retrieval_reason="Regression test with USE_CONNECTION_SECRET_RETRIEVER=true",
|
|
848
849
|
)
|
|
849
850
|
print_success("Successfully retrieved unmasked secrets from database")
|
|
850
|
-
except
|
|
851
|
-
|
|
851
|
+
except SecretRetrievalError as e:
|
|
852
|
+
write_github_output("success", False)
|
|
853
|
+
write_github_output("error", str(e))
|
|
852
854
|
exit_with_error(
|
|
853
|
-
f"
|
|
854
|
-
f"
|
|
855
|
-
f"
|
|
856
|
-
f"
|
|
855
|
+
f"{e}\n\n"
|
|
856
|
+
f"This connection cannot be used for regression testing. "
|
|
857
|
+
f"Please use a connection from a non-EU workspace, or use GSM-based "
|
|
858
|
+
f"integration test credentials instead (by omitting --connection-id)."
|
|
857
859
|
)
|
|
858
860
|
|
|
859
861
|
config_file, catalog_file = save_connection_data_to_files(
|
airbyte_ops_mcp/constants.py
CHANGED
|
@@ -10,6 +10,19 @@ from airbyte.exceptions import PyAirbyteInputError
|
|
|
10
10
|
MCP_SERVER_NAME = "airbyte-internal-ops"
|
|
11
11
|
"""The name of the MCP server."""
|
|
12
12
|
|
|
13
|
+
|
|
14
|
+
class ServerConfigKey(StrEnum):
|
|
15
|
+
"""Config keys for MCP server configuration arguments.
|
|
16
|
+
|
|
17
|
+
These keys are used both when defining server_config_args in mcp_server()
|
|
18
|
+
and when retrieving config values via get_mcp_config().
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
BEARER_TOKEN = "bearer_token"
|
|
22
|
+
CLIENT_ID = "client_id"
|
|
23
|
+
CLIENT_SECRET = "client_secret"
|
|
24
|
+
|
|
25
|
+
|
|
13
26
|
USER_AGENT = "Airbyte-Internal-Ops Python client"
|
|
14
27
|
"""User-Agent string for HTTP requests to Airbyte Cloud APIs."""
|
|
15
28
|
|
airbyte_ops_mcp/mcp/__init__.py
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
"""MCP tools organized by functional domain.
|
|
2
2
|
|
|
3
|
-
This package contains all MCP tool implementations organized by
|
|
4
|
-
|
|
5
|
-
-
|
|
6
|
-
-
|
|
7
|
-
-
|
|
8
|
-
-
|
|
3
|
+
This package contains all MCP tool implementations organized by module name.
|
|
4
|
+
Domain is automatically inferred from the file stem where tools are defined:
|
|
5
|
+
- github_repo_ops: GitHub repository operations
|
|
6
|
+
- cloud_connector_versions: Cloud connector version management
|
|
7
|
+
- prod_db_queries: Production database queries
|
|
8
|
+
- github_actions: GitHub Actions workflow operations
|
|
9
|
+
- regression_tests: Connector regression testing
|
|
10
|
+
- prerelease: Connector prerelease operations
|
|
11
|
+
- gcp_logs: GCP log queries
|
|
9
12
|
"""
|
|
@@ -15,7 +15,8 @@ from typing import Annotated, Literal
|
|
|
15
15
|
|
|
16
16
|
from airbyte import constants
|
|
17
17
|
from airbyte.exceptions import PyAirbyteInputError
|
|
18
|
-
from fastmcp import FastMCP
|
|
18
|
+
from fastmcp import Context, FastMCP
|
|
19
|
+
from fastmcp_extensions import get_mcp_config, mcp_tool, register_mcp_tools
|
|
19
20
|
from pydantic import Field
|
|
20
21
|
|
|
21
22
|
from airbyte_ops_mcp.cloud_admin import api_client
|
|
@@ -29,19 +30,13 @@ from airbyte_ops_mcp.cloud_admin.models import (
|
|
|
29
30
|
VersionOverrideOperationResult,
|
|
30
31
|
WorkspaceVersionOverrideResult,
|
|
31
32
|
)
|
|
32
|
-
from airbyte_ops_mcp.constants import WorkspaceAliasEnum
|
|
33
|
+
from airbyte_ops_mcp.constants import ServerConfigKey, WorkspaceAliasEnum
|
|
33
34
|
from airbyte_ops_mcp.github_api import (
|
|
34
35
|
GitHubAPIError,
|
|
35
36
|
GitHubCommentParseError,
|
|
36
37
|
GitHubUserEmailNotFoundError,
|
|
37
38
|
get_admin_email_from_approval_comment,
|
|
38
39
|
)
|
|
39
|
-
from airbyte_ops_mcp.mcp._http_headers import (
|
|
40
|
-
resolve_bearer_token,
|
|
41
|
-
resolve_client_id,
|
|
42
|
-
resolve_client_secret,
|
|
43
|
-
)
|
|
44
|
-
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
45
40
|
|
|
46
41
|
|
|
47
42
|
@dataclass(frozen=True)
|
|
@@ -56,33 +51,36 @@ class _ResolvedCloudAuth:
|
|
|
56
51
|
client_secret: str | None = None
|
|
57
52
|
|
|
58
53
|
|
|
59
|
-
def _resolve_cloud_auth() -> _ResolvedCloudAuth:
|
|
54
|
+
def _resolve_cloud_auth(ctx: Context) -> _ResolvedCloudAuth:
|
|
60
55
|
"""Resolve authentication credentials for Airbyte Cloud API.
|
|
61
56
|
|
|
62
57
|
Credentials are resolved in priority order:
|
|
63
58
|
1. Bearer token (Authorization header or AIRBYTE_CLOUD_BEARER_TOKEN env var)
|
|
64
59
|
2. Client credentials (X-Airbyte-Cloud-Client-Id/Secret headers or env vars)
|
|
65
60
|
|
|
61
|
+
Args:
|
|
62
|
+
ctx: FastMCP Context object from the current tool invocation.
|
|
63
|
+
|
|
66
64
|
Returns:
|
|
67
65
|
_ResolvedCloudAuth with either bearer_token or client credentials set.
|
|
68
66
|
|
|
69
67
|
Raises:
|
|
70
68
|
CloudAuthError: If credentials cannot be resolved from headers or env vars.
|
|
71
69
|
"""
|
|
72
|
-
# Try bearer token first (preferred)
|
|
73
|
-
bearer_token =
|
|
70
|
+
# Try bearer token first (preferred, but not required)
|
|
71
|
+
bearer_token = get_mcp_config(ctx, ServerConfigKey.BEARER_TOKEN)
|
|
74
72
|
if bearer_token:
|
|
75
|
-
return _ResolvedCloudAuth(bearer_token=
|
|
73
|
+
return _ResolvedCloudAuth(bearer_token=bearer_token)
|
|
76
74
|
|
|
77
75
|
# Fall back to client credentials
|
|
78
76
|
try:
|
|
79
|
-
client_id =
|
|
80
|
-
client_secret =
|
|
77
|
+
client_id = get_mcp_config(ctx, ServerConfigKey.CLIENT_ID)
|
|
78
|
+
client_secret = get_mcp_config(ctx, ServerConfigKey.CLIENT_SECRET)
|
|
81
79
|
return _ResolvedCloudAuth(
|
|
82
|
-
client_id=
|
|
83
|
-
client_secret=
|
|
80
|
+
client_id=client_id,
|
|
81
|
+
client_secret=client_secret,
|
|
84
82
|
)
|
|
85
|
-
except
|
|
83
|
+
except ValueError as e:
|
|
86
84
|
raise CloudAuthError(
|
|
87
85
|
f"Failed to resolve credentials. Ensure credentials are provided "
|
|
88
86
|
f"via Authorization header (Bearer token), "
|
|
@@ -111,6 +109,8 @@ def get_cloud_connector_version(
|
|
|
111
109
|
Literal["source", "destination"],
|
|
112
110
|
"The type of connector (source or destination)",
|
|
113
111
|
],
|
|
112
|
+
*,
|
|
113
|
+
ctx: Context,
|
|
114
114
|
) -> ConnectorVersionInfo:
|
|
115
115
|
"""Get the current version information for a deployed connector.
|
|
116
116
|
|
|
@@ -126,7 +126,7 @@ def get_cloud_connector_version(
|
|
|
126
126
|
resolved_workspace_id = WorkspaceAliasEnum.resolve(workspace_id)
|
|
127
127
|
|
|
128
128
|
try:
|
|
129
|
-
auth = _resolve_cloud_auth()
|
|
129
|
+
auth = _resolve_cloud_auth(ctx)
|
|
130
130
|
|
|
131
131
|
# Use vendored API client instead of connector.get_connector_version()
|
|
132
132
|
# Use Config API root for version management operations
|
|
@@ -248,6 +248,8 @@ def set_cloud_connector_version_override(
|
|
|
248
248
|
default=None,
|
|
249
249
|
),
|
|
250
250
|
],
|
|
251
|
+
*,
|
|
252
|
+
ctx: Context,
|
|
251
253
|
) -> VersionOverrideOperationResult:
|
|
252
254
|
"""Set or clear a version override for a deployed connector.
|
|
253
255
|
|
|
@@ -359,7 +361,7 @@ def set_cloud_connector_version_override(
|
|
|
359
361
|
|
|
360
362
|
# Resolve auth and get current version info
|
|
361
363
|
try:
|
|
362
|
-
auth = _resolve_cloud_auth()
|
|
364
|
+
auth = _resolve_cloud_auth(ctx)
|
|
363
365
|
|
|
364
366
|
# Get current version info before the operation
|
|
365
367
|
current_version_data = api_client.get_connector_version(
|
|
@@ -529,6 +531,8 @@ def set_workspace_connector_version_override(
|
|
|
529
531
|
default=None,
|
|
530
532
|
),
|
|
531
533
|
],
|
|
534
|
+
*,
|
|
535
|
+
ctx: Context,
|
|
532
536
|
) -> WorkspaceVersionOverrideResult:
|
|
533
537
|
"""Set or clear a workspace-level version override for a connector type.
|
|
534
538
|
|
|
@@ -634,7 +638,7 @@ def set_workspace_connector_version_override(
|
|
|
634
638
|
|
|
635
639
|
# Resolve auth and call API client
|
|
636
640
|
try:
|
|
637
|
-
auth = _resolve_cloud_auth()
|
|
641
|
+
auth = _resolve_cloud_auth(ctx)
|
|
638
642
|
|
|
639
643
|
result = api_client.set_workspace_connector_version_override(
|
|
640
644
|
workspace_id=resolved_workspace_id,
|
|
@@ -762,6 +766,8 @@ def set_organization_connector_version_override(
|
|
|
762
766
|
default=None,
|
|
763
767
|
),
|
|
764
768
|
],
|
|
769
|
+
*,
|
|
770
|
+
ctx: Context,
|
|
765
771
|
) -> OrganizationVersionOverrideResult:
|
|
766
772
|
"""Set or clear an organization-level version override for a connector type.
|
|
767
773
|
|
|
@@ -863,7 +869,7 @@ def set_organization_connector_version_override(
|
|
|
863
869
|
|
|
864
870
|
# Resolve auth and call API client
|
|
865
871
|
try:
|
|
866
|
-
auth = _resolve_cloud_auth()
|
|
872
|
+
auth = _resolve_cloud_auth(ctx)
|
|
867
873
|
|
|
868
874
|
result = api_client.set_organization_connector_version_override(
|
|
869
875
|
organization_id=organization_id,
|
|
@@ -921,4 +927,4 @@ def register_cloud_connector_version_tools(app: FastMCP) -> None:
|
|
|
921
927
|
Args:
|
|
922
928
|
app: FastMCP application instance
|
|
923
929
|
"""
|
|
924
|
-
register_mcp_tools(app,
|
|
930
|
+
register_mcp_tools(app, mcp_module=__name__)
|
airbyte_ops_mcp/mcp/gcp_logs.py
CHANGED
|
@@ -10,6 +10,7 @@ from __future__ import annotations
|
|
|
10
10
|
from typing import Annotated
|
|
11
11
|
|
|
12
12
|
from fastmcp import FastMCP
|
|
13
|
+
from fastmcp_extensions import mcp_tool, register_mcp_tools
|
|
13
14
|
from pydantic import Field
|
|
14
15
|
|
|
15
16
|
from airbyte_ops_mcp.gcp_logs import (
|
|
@@ -18,7 +19,6 @@ from airbyte_ops_mcp.gcp_logs import (
|
|
|
18
19
|
fetch_error_logs,
|
|
19
20
|
)
|
|
20
21
|
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
22
|
|
|
23
23
|
|
|
24
24
|
@mcp_tool(
|
|
@@ -12,6 +12,7 @@ from typing import Annotated
|
|
|
12
12
|
|
|
13
13
|
import requests
|
|
14
14
|
from fastmcp import FastMCP
|
|
15
|
+
from fastmcp_extensions import mcp_tool, register_mcp_tools
|
|
15
16
|
from pydantic import BaseModel, Field
|
|
16
17
|
|
|
17
18
|
from airbyte_ops_mcp.github_actions import (
|
|
@@ -23,7 +24,6 @@ from airbyte_ops_mcp.github_api import (
|
|
|
23
24
|
get_pr_head_ref,
|
|
24
25
|
resolve_github_token,
|
|
25
26
|
)
|
|
26
|
-
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
27
27
|
|
|
28
28
|
# Token env vars for workflow triggering (in order of preference)
|
|
29
29
|
WORKFLOW_TRIGGER_TOKEN_ENV_VARS = [
|
|
@@ -400,4 +400,4 @@ def register_github_actions_tools(app: FastMCP) -> None:
|
|
|
400
400
|
Args:
|
|
401
401
|
app: FastMCP application instance
|
|
402
402
|
"""
|
|
403
|
-
register_mcp_tools(app,
|
|
403
|
+
register_mcp_tools(app, mcp_module=__name__)
|
|
@@ -10,12 +10,12 @@ from __future__ import annotations
|
|
|
10
10
|
from typing import Annotated, Literal
|
|
11
11
|
|
|
12
12
|
from fastmcp import FastMCP
|
|
13
|
+
from fastmcp_extensions import mcp_tool, register_mcp_tools
|
|
13
14
|
from pydantic import BaseModel
|
|
14
15
|
|
|
15
16
|
from airbyte_ops_mcp.airbyte_repo.bump_version import bump_connector_version
|
|
16
17
|
from airbyte_ops_mcp.airbyte_repo.list_connectors import list_connectors
|
|
17
18
|
from airbyte_ops_mcp.airbyte_repo.utils import resolve_diff_range
|
|
18
|
-
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
19
19
|
|
|
20
20
|
|
|
21
21
|
class ConnectorListResponse(BaseModel):
|
|
@@ -162,4 +162,4 @@ def register_github_repo_ops_tools(app: FastMCP) -> None:
|
|
|
162
162
|
Args:
|
|
163
163
|
app: FastMCP application instance
|
|
164
164
|
"""
|
|
165
|
-
register_mcp_tools(app,
|
|
165
|
+
register_mcp_tools(app, mcp_module=__name__)
|
|
@@ -16,6 +16,7 @@ from typing import Annotated, Literal
|
|
|
16
16
|
import requests
|
|
17
17
|
import yaml
|
|
18
18
|
from fastmcp import FastMCP
|
|
19
|
+
from fastmcp_extensions import mcp_tool, register_mcp_tools
|
|
19
20
|
from pydantic import BaseModel, Field
|
|
20
21
|
|
|
21
22
|
from airbyte_ops_mcp.github_actions import trigger_workflow_dispatch
|
|
@@ -24,7 +25,6 @@ from airbyte_ops_mcp.github_api import (
|
|
|
24
25
|
get_pr_head_ref,
|
|
25
26
|
resolve_github_token,
|
|
26
27
|
)
|
|
27
|
-
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
28
28
|
|
|
29
29
|
|
|
30
30
|
class ConnectorRepo(StrEnum):
|
|
@@ -278,4 +278,4 @@ def register_prerelease_tools(app: FastMCP) -> None:
|
|
|
278
278
|
Args:
|
|
279
279
|
app: FastMCP application instance
|
|
280
280
|
"""
|
|
281
|
-
register_mcp_tools(app,
|
|
281
|
+
register_mcp_tools(app, mcp_module=__name__)
|
|
@@ -14,10 +14,10 @@ from typing import Annotated, Any
|
|
|
14
14
|
import requests
|
|
15
15
|
from airbyte.exceptions import PyAirbyteInputError
|
|
16
16
|
from fastmcp import FastMCP
|
|
17
|
+
from fastmcp_extensions import mcp_tool, register_mcp_tools
|
|
17
18
|
from pydantic import BaseModel, Field
|
|
18
19
|
|
|
19
20
|
from airbyte_ops_mcp.constants import OrganizationAliasEnum, WorkspaceAliasEnum
|
|
20
|
-
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
21
21
|
from airbyte_ops_mcp.prod_db_access.queries import (
|
|
22
22
|
query_actors_pinned_to_version,
|
|
23
23
|
query_connections_by_connector,
|
|
@@ -1274,4 +1274,4 @@ def query_prod_connector_connection_stats(
|
|
|
1274
1274
|
|
|
1275
1275
|
def register_prod_db_query_tools(app: FastMCP) -> None:
|
|
1276
1276
|
"""Register prod DB query tools with the FastMCP app."""
|
|
1277
|
-
register_mcp_tools(app,
|
|
1277
|
+
register_mcp_tools(app, mcp_module=__name__)
|
airbyte_ops_mcp/mcp/prompts.py
CHANGED
|
@@ -10,10 +10,10 @@ from __future__ import annotations
|
|
|
10
10
|
from typing import Annotated
|
|
11
11
|
|
|
12
12
|
from fastmcp import FastMCP
|
|
13
|
+
from fastmcp_extensions import mcp_prompt, register_mcp_prompts
|
|
13
14
|
from pydantic import Field
|
|
14
15
|
|
|
15
16
|
from airbyte_ops_mcp.mcp._guidance import TEST_MY_TOOLS_GUIDANCE
|
|
16
|
-
from airbyte_ops_mcp.mcp._mcp_utils import mcp_prompt, register_mcp_prompts
|
|
17
17
|
|
|
18
18
|
|
|
19
19
|
@mcp_prompt(
|
|
@@ -56,4 +56,4 @@ def register_prompts(app: FastMCP) -> None:
|
|
|
56
56
|
Args:
|
|
57
57
|
app: FastMCP application instance
|
|
58
58
|
"""
|
|
59
|
-
register_mcp_prompts(app,
|
|
59
|
+
register_mcp_prompts(app, mcp_module=__name__)
|
|
@@ -29,12 +29,12 @@ from airbyte.exceptions import (
|
|
|
29
29
|
AirbyteWorkspaceMismatchError,
|
|
30
30
|
)
|
|
31
31
|
from fastmcp import FastMCP
|
|
32
|
+
from fastmcp_extensions import mcp_tool, register_mcp_tools
|
|
32
33
|
from pydantic import BaseModel, Field
|
|
33
34
|
|
|
34
35
|
from airbyte_ops_mcp.constants import WorkspaceAliasEnum
|
|
35
36
|
from airbyte_ops_mcp.github_actions import trigger_workflow_dispatch
|
|
36
37
|
from airbyte_ops_mcp.github_api import GITHUB_API_BASE, resolve_github_token
|
|
37
|
-
from airbyte_ops_mcp.mcp._mcp_utils import mcp_tool, register_mcp_tools
|
|
38
38
|
from airbyte_ops_mcp.mcp.prerelease import ConnectorRepo
|
|
39
39
|
|
|
40
40
|
logger = logging.getLogger(__name__)
|
|
@@ -471,4 +471,4 @@ def register_regression_tests_tools(app: FastMCP) -> None:
|
|
|
471
471
|
Args:
|
|
472
472
|
app: FastMCP application instance
|
|
473
473
|
"""
|
|
474
|
-
register_mcp_tools(app,
|
|
474
|
+
register_mcp_tools(app, mcp_module=__name__)
|
airbyte_ops_mcp/mcp/server.py
CHANGED
|
@@ -17,10 +17,17 @@ import os
|
|
|
17
17
|
import sys
|
|
18
18
|
from pathlib import Path
|
|
19
19
|
|
|
20
|
+
from airbyte.cloud.auth import resolve_cloud_client_id, resolve_cloud_client_secret
|
|
20
21
|
from dotenv import load_dotenv
|
|
21
22
|
from fastmcp import FastMCP
|
|
23
|
+
from fastmcp_extensions import MCPServerConfigArg, mcp_server
|
|
22
24
|
|
|
23
|
-
from airbyte_ops_mcp.constants import
|
|
25
|
+
from airbyte_ops_mcp.constants import (
|
|
26
|
+
HEADER_AIRBYTE_CLOUD_CLIENT_ID,
|
|
27
|
+
HEADER_AIRBYTE_CLOUD_CLIENT_SECRET,
|
|
28
|
+
MCP_SERVER_NAME,
|
|
29
|
+
ServerConfigKey,
|
|
30
|
+
)
|
|
24
31
|
from airbyte_ops_mcp.mcp.cloud_connector_versions import (
|
|
25
32
|
register_cloud_connector_version_tools,
|
|
26
33
|
)
|
|
@@ -31,20 +38,64 @@ from airbyte_ops_mcp.mcp.prerelease import register_prerelease_tools
|
|
|
31
38
|
from airbyte_ops_mcp.mcp.prod_db_queries import register_prod_db_query_tools
|
|
32
39
|
from airbyte_ops_mcp.mcp.prompts import register_prompts
|
|
33
40
|
from airbyte_ops_mcp.mcp.regression_tests import register_regression_tests_tools
|
|
34
|
-
from airbyte_ops_mcp.mcp.server_info import register_server_info_resources
|
|
35
41
|
|
|
36
42
|
# Default HTTP server configuration
|
|
37
43
|
DEFAULT_HTTP_HOST = "127.0.0.1"
|
|
38
44
|
DEFAULT_HTTP_PORT = 8082
|
|
39
45
|
|
|
40
|
-
|
|
46
|
+
|
|
47
|
+
def _normalize_bearer_token(value: str) -> str | None:
|
|
48
|
+
"""Extract bearer token from Authorization header value.
|
|
49
|
+
|
|
50
|
+
Parses "Bearer <token>" format (case-insensitive prefix).
|
|
51
|
+
Returns None if the value doesn't have the Bearer prefix.
|
|
52
|
+
"""
|
|
53
|
+
if value.lower().startswith("bearer "):
|
|
54
|
+
token = value[7:].strip()
|
|
55
|
+
return token if token else None
|
|
56
|
+
return None
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# Create the MCP server with built-in server info resource
|
|
60
|
+
app = mcp_server(
|
|
61
|
+
name=MCP_SERVER_NAME,
|
|
62
|
+
package_name="airbyte-internal-ops",
|
|
63
|
+
advertised_properties={
|
|
64
|
+
"docs_url": "https://github.com/airbytehq/airbyte-ops-mcp",
|
|
65
|
+
"release_history_url": "https://github.com/airbytehq/airbyte-ops-mcp/releases",
|
|
66
|
+
},
|
|
67
|
+
server_config_args=[
|
|
68
|
+
MCPServerConfigArg(
|
|
69
|
+
name=ServerConfigKey.BEARER_TOKEN,
|
|
70
|
+
http_header_key="Authorization",
|
|
71
|
+
env_var="AIRBYTE_CLOUD_BEARER_TOKEN",
|
|
72
|
+
normalize_fn=_normalize_bearer_token,
|
|
73
|
+
required=False,
|
|
74
|
+
sensitive=True,
|
|
75
|
+
),
|
|
76
|
+
MCPServerConfigArg(
|
|
77
|
+
name=ServerConfigKey.CLIENT_ID,
|
|
78
|
+
http_header_key=HEADER_AIRBYTE_CLOUD_CLIENT_ID,
|
|
79
|
+
default=lambda: str(resolve_cloud_client_id()),
|
|
80
|
+
required=True,
|
|
81
|
+
sensitive=True,
|
|
82
|
+
),
|
|
83
|
+
MCPServerConfigArg(
|
|
84
|
+
name=ServerConfigKey.CLIENT_SECRET,
|
|
85
|
+
http_header_key=HEADER_AIRBYTE_CLOUD_CLIENT_SECRET,
|
|
86
|
+
default=lambda: str(resolve_cloud_client_secret()),
|
|
87
|
+
required=True,
|
|
88
|
+
sensitive=True,
|
|
89
|
+
),
|
|
90
|
+
],
|
|
91
|
+
include_standard_tool_filters=True,
|
|
92
|
+
)
|
|
41
93
|
|
|
42
94
|
|
|
43
95
|
def register_server_assets(app: FastMCP) -> None:
|
|
44
96
|
"""Register all server assets (tools, prompts, resources) with the FastMCP app.
|
|
45
97
|
|
|
46
98
|
This function registers assets for all domains:
|
|
47
|
-
- SERVER_INFO: Server version and information resources
|
|
48
99
|
- REPO: GitHub repository operations
|
|
49
100
|
- CLOUD: Cloud connector version management
|
|
50
101
|
- PROMPTS: Prompt templates for common workflows
|
|
@@ -54,10 +105,11 @@ def register_server_assets(app: FastMCP) -> None:
|
|
|
54
105
|
- QA: Connector quality assurance (future)
|
|
55
106
|
- INSIGHTS: Connector analysis and insights (future)
|
|
56
107
|
|
|
108
|
+
Note: Server info resource is now built-in via mcp_server() helper.
|
|
109
|
+
|
|
57
110
|
Args:
|
|
58
111
|
app: FastMCP application instance
|
|
59
112
|
"""
|
|
60
|
-
register_server_info_resources(app)
|
|
61
113
|
register_github_repo_ops_tools(app)
|
|
62
114
|
register_github_actions_tools(app)
|
|
63
115
|
register_prerelease_tools(app)
|
|
@@ -10,6 +10,7 @@ from airbyte_ops_mcp.regression_tests.connection_fetcher import (
|
|
|
10
10
|
fetch_connection_data,
|
|
11
11
|
)
|
|
12
12
|
from airbyte_ops_mcp.regression_tests.connection_secret_retriever import (
|
|
13
|
+
SecretRetrievalError,
|
|
13
14
|
enrich_config_with_secrets,
|
|
14
15
|
is_secret_retriever_enabled,
|
|
15
16
|
retrieve_unmasked_config,
|
|
@@ -27,6 +28,7 @@ __all__ = [
|
|
|
27
28
|
"ConnectionData",
|
|
28
29
|
"ConnectorUnderTest",
|
|
29
30
|
"ExecutionResult",
|
|
31
|
+
"SecretRetrievalError",
|
|
30
32
|
"TargetOrControl",
|
|
31
33
|
"enrich_config_with_secrets",
|
|
32
34
|
"fetch_connection_data",
|
|
@@ -107,9 +107,18 @@ def retrieve_unmasked_config(
|
|
|
107
107
|
return None
|
|
108
108
|
|
|
109
109
|
|
|
110
|
+
class SecretRetrievalError(Exception):
|
|
111
|
+
"""Raised when secret retrieval fails.
|
|
112
|
+
|
|
113
|
+
This exception is raised when USE_CONNECTION_SECRET_RETRIEVER is enabled
|
|
114
|
+
but secrets cannot be retrieved (e.g., EU data residency restrictions).
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
|
|
110
118
|
def enrich_config_with_secrets(
|
|
111
119
|
connection_data: ConnectionData,
|
|
112
120
|
retrieval_reason: str = "MCP live tests",
|
|
121
|
+
raise_on_failure: bool = True,
|
|
113
122
|
) -> ConnectionData:
|
|
114
123
|
"""Enrich connection data with unmasked secrets from internal retriever.
|
|
115
124
|
|
|
@@ -120,10 +129,16 @@ def enrich_config_with_secrets(
|
|
|
120
129
|
Args:
|
|
121
130
|
connection_data: The connection data to enrich.
|
|
122
131
|
retrieval_reason: Reason for retrieval (for audit logging).
|
|
132
|
+
raise_on_failure: If True (default), raise SecretRetrievalError when
|
|
133
|
+
secrets cannot be retrieved. If False, return the original
|
|
134
|
+
connection_data with masked secrets (legacy behavior).
|
|
123
135
|
|
|
124
136
|
Returns:
|
|
125
|
-
A new ConnectionData with unmasked config
|
|
126
|
-
|
|
137
|
+
A new ConnectionData with unmasked config.
|
|
138
|
+
|
|
139
|
+
Raises:
|
|
140
|
+
SecretRetrievalError: If raise_on_failure is True and secrets cannot
|
|
141
|
+
be retrieved (e.g., due to EU data residency restrictions).
|
|
127
142
|
"""
|
|
128
143
|
unmasked_config = retrieve_unmasked_config(
|
|
129
144
|
connection_id=connection_data.connection_id,
|
|
@@ -131,10 +146,15 @@ def enrich_config_with_secrets(
|
|
|
131
146
|
)
|
|
132
147
|
|
|
133
148
|
if unmasked_config is None:
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
"
|
|
149
|
+
error_msg = (
|
|
150
|
+
"Could not retrieve unmasked secrets for connection "
|
|
151
|
+
f"{connection_data.connection_id}. This may be due to EU data "
|
|
152
|
+
"residency restrictions or database connectivity issues. "
|
|
153
|
+
"The connection's credentials cannot be used for regression testing."
|
|
137
154
|
)
|
|
155
|
+
logger.warning(error_msg)
|
|
156
|
+
if raise_on_failure:
|
|
157
|
+
raise SecretRetrievalError(error_msg)
|
|
138
158
|
return connection_data
|
|
139
159
|
|
|
140
160
|
logger.info(
|