airbyte-internal-ops 0.5.2__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.2.dist-info → airbyte_internal_ops-0.6.0.dist-info}/METADATA +2 -1
- {airbyte_internal_ops-0.5.2.dist-info → airbyte_internal_ops-0.6.0.dist-info}/RECORD +15 -19
- 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/_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.2.dist-info → airbyte_internal_ops-0.6.0.dist-info}/WHEEL +0 -0
- {airbyte_internal_ops-0.5.2.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
|
|
@@ -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
|
|
@@ -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/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)
|
airbyte_ops_mcp/_annotations.py
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
-
"""MCP tool annotation constants.
|
|
3
|
-
|
|
4
|
-
These constants define the standard MCP annotations for tools, following the
|
|
5
|
-
FastMCP 2.2.7+ specification.
|
|
6
|
-
|
|
7
|
-
For more information, see:
|
|
8
|
-
https://gofastmcp.com/concepts/tools#mcp-annotations
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
from __future__ import annotations
|
|
12
|
-
|
|
13
|
-
READ_ONLY_HINT = "readOnlyHint"
|
|
14
|
-
"""Indicates if the tool only reads data without making any changes.
|
|
15
|
-
|
|
16
|
-
When True, the tool performs read-only operations and does not modify any state.
|
|
17
|
-
When False, the tool may write, create, update, or delete data.
|
|
18
|
-
|
|
19
|
-
FastMCP default if not specified: False
|
|
20
|
-
"""
|
|
21
|
-
|
|
22
|
-
DESTRUCTIVE_HINT = "destructiveHint"
|
|
23
|
-
"""Signals if the tool's changes are destructive (updates or deletes existing data).
|
|
24
|
-
|
|
25
|
-
This hint is only relevant for non-read-only tools (readOnlyHint=False).
|
|
26
|
-
When True, the tool modifies or deletes existing data in a way that may be
|
|
27
|
-
difficult or impossible to reverse.
|
|
28
|
-
When False, the tool creates new data or performs non-destructive operations.
|
|
29
|
-
|
|
30
|
-
FastMCP default if not specified: True
|
|
31
|
-
"""
|
|
32
|
-
|
|
33
|
-
IDEMPOTENT_HINT = "idempotentHint"
|
|
34
|
-
"""Indicates if repeated calls with the same parameters have the same effect.
|
|
35
|
-
|
|
36
|
-
When True, calling the tool multiple times with identical parameters produces
|
|
37
|
-
the same result and side effects as calling it once.
|
|
38
|
-
When False, each call may produce different results or side effects.
|
|
39
|
-
|
|
40
|
-
FastMCP default if not specified: False
|
|
41
|
-
"""
|
|
42
|
-
|
|
43
|
-
OPEN_WORLD_HINT = "openWorldHint"
|
|
44
|
-
"""Specifies if the tool interacts with external systems.
|
|
45
|
-
|
|
46
|
-
When True, the tool communicates with external services, APIs, or systems
|
|
47
|
-
outside the local environment (e.g., cloud APIs, remote databases, internet).
|
|
48
|
-
When False, the tool only operates on local state or resources.
|
|
49
|
-
|
|
50
|
-
FastMCP default if not specified: True
|
|
51
|
-
"""
|
|
@@ -1,254 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
-
"""HTTP header extraction for Airbyte Cloud credentials.
|
|
3
|
-
|
|
4
|
-
This module provides internal helper functions for extracting Airbyte Cloud
|
|
5
|
-
authentication credentials from HTTP headers when running as an MCP HTTP server.
|
|
6
|
-
This enables per-request credential passing from upstream services like coral-agents.
|
|
7
|
-
|
|
8
|
-
The resolution order for credentials is:
|
|
9
|
-
1. HTTP headers (when running as MCP HTTP server)
|
|
10
|
-
2. Environment variables (fallback)
|
|
11
|
-
|
|
12
|
-
Note: This module is prefixed with "_" to indicate it is internal helper logic
|
|
13
|
-
for the MCP module and should not be imported directly by external code.
|
|
14
|
-
"""
|
|
15
|
-
|
|
16
|
-
from __future__ import annotations
|
|
17
|
-
|
|
18
|
-
import os
|
|
19
|
-
|
|
20
|
-
from airbyte.cloud.auth import (
|
|
21
|
-
resolve_cloud_api_url,
|
|
22
|
-
resolve_cloud_client_id,
|
|
23
|
-
resolve_cloud_client_secret,
|
|
24
|
-
resolve_cloud_workspace_id,
|
|
25
|
-
)
|
|
26
|
-
from airbyte.secrets.base import SecretString
|
|
27
|
-
from fastmcp.server.dependencies import get_http_headers
|
|
28
|
-
|
|
29
|
-
from airbyte_ops_mcp.constants import (
|
|
30
|
-
HEADER_AIRBYTE_CLOUD_API_URL,
|
|
31
|
-
HEADER_AIRBYTE_CLOUD_CLIENT_ID,
|
|
32
|
-
HEADER_AIRBYTE_CLOUD_CLIENT_SECRET,
|
|
33
|
-
HEADER_AIRBYTE_CLOUD_WORKSPACE_ID,
|
|
34
|
-
)
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
def _get_header_value(headers: dict[str, str], header_name: str) -> str | None:
|
|
38
|
-
"""Get a header value from a headers dict, case-insensitively.
|
|
39
|
-
|
|
40
|
-
Args:
|
|
41
|
-
headers: Dictionary of HTTP headers.
|
|
42
|
-
header_name: The header name to look for (case-insensitive).
|
|
43
|
-
|
|
44
|
-
Returns:
|
|
45
|
-
The header value if found, None otherwise.
|
|
46
|
-
"""
|
|
47
|
-
header_name_lower = header_name.lower()
|
|
48
|
-
for key, value in headers.items():
|
|
49
|
-
if key.lower() == header_name_lower:
|
|
50
|
-
return value
|
|
51
|
-
return None
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
def get_bearer_token_from_headers() -> SecretString | None:
|
|
55
|
-
"""Extract bearer token from HTTP Authorization header.
|
|
56
|
-
|
|
57
|
-
This function extracts the bearer token from the standard HTTP
|
|
58
|
-
`Authorization: Bearer <token>` header when running as an MCP HTTP server.
|
|
59
|
-
|
|
60
|
-
Returns:
|
|
61
|
-
The bearer token as a SecretString, or None if not found or not in HTTP context.
|
|
62
|
-
"""
|
|
63
|
-
headers = get_http_headers()
|
|
64
|
-
if not headers:
|
|
65
|
-
return None
|
|
66
|
-
|
|
67
|
-
auth_header = _get_header_value(headers, "Authorization")
|
|
68
|
-
if not auth_header:
|
|
69
|
-
return None
|
|
70
|
-
|
|
71
|
-
# Parse "Bearer <token>" format (case-insensitive prefix check)
|
|
72
|
-
bearer_prefix = "bearer "
|
|
73
|
-
if auth_header.lower().startswith(bearer_prefix):
|
|
74
|
-
token = auth_header[len(bearer_prefix) :].strip()
|
|
75
|
-
if token:
|
|
76
|
-
return SecretString(token)
|
|
77
|
-
|
|
78
|
-
return None
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def get_client_id_from_headers() -> SecretString | None:
|
|
82
|
-
"""Extract client ID from HTTP headers.
|
|
83
|
-
|
|
84
|
-
Returns:
|
|
85
|
-
The client ID as a SecretString, or None if not found or not in HTTP context.
|
|
86
|
-
"""
|
|
87
|
-
headers = get_http_headers()
|
|
88
|
-
if not headers:
|
|
89
|
-
return None
|
|
90
|
-
|
|
91
|
-
value = _get_header_value(headers, HEADER_AIRBYTE_CLOUD_CLIENT_ID)
|
|
92
|
-
if value:
|
|
93
|
-
return SecretString(value)
|
|
94
|
-
return None
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def get_client_secret_from_headers() -> SecretString | None:
|
|
98
|
-
"""Extract client secret from HTTP headers.
|
|
99
|
-
|
|
100
|
-
Returns:
|
|
101
|
-
The client secret as a SecretString, or None if not found or not in HTTP context.
|
|
102
|
-
"""
|
|
103
|
-
headers = get_http_headers()
|
|
104
|
-
if not headers:
|
|
105
|
-
return None
|
|
106
|
-
|
|
107
|
-
value = _get_header_value(headers, HEADER_AIRBYTE_CLOUD_CLIENT_SECRET)
|
|
108
|
-
if value:
|
|
109
|
-
return SecretString(value)
|
|
110
|
-
return None
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
def get_workspace_id_from_headers() -> str | None:
|
|
114
|
-
"""Extract workspace ID from HTTP headers.
|
|
115
|
-
|
|
116
|
-
Returns:
|
|
117
|
-
The workspace ID, or None if not found or not in HTTP context.
|
|
118
|
-
"""
|
|
119
|
-
headers = get_http_headers()
|
|
120
|
-
if not headers:
|
|
121
|
-
return None
|
|
122
|
-
|
|
123
|
-
return _get_header_value(headers, HEADER_AIRBYTE_CLOUD_WORKSPACE_ID)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def get_api_url_from_headers() -> str | None:
|
|
127
|
-
"""Extract API URL from HTTP headers.
|
|
128
|
-
|
|
129
|
-
Returns:
|
|
130
|
-
The API URL, or None if not found or not in HTTP context.
|
|
131
|
-
"""
|
|
132
|
-
headers = get_http_headers()
|
|
133
|
-
if not headers:
|
|
134
|
-
return None
|
|
135
|
-
|
|
136
|
-
return _get_header_value(headers, HEADER_AIRBYTE_CLOUD_API_URL)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
def resolve_client_id() -> SecretString:
|
|
140
|
-
"""Resolve client ID from HTTP headers or environment variables.
|
|
141
|
-
|
|
142
|
-
Resolution order:
|
|
143
|
-
1. HTTP header X-Airbyte-Cloud-Client-Id
|
|
144
|
-
2. Environment variable AIRBYTE_CLOUD_CLIENT_ID (via PyAirbyte)
|
|
145
|
-
|
|
146
|
-
Returns:
|
|
147
|
-
The resolved client ID as a SecretString.
|
|
148
|
-
|
|
149
|
-
Raises:
|
|
150
|
-
PyAirbyteSecretNotFoundError: If no client ID can be resolved.
|
|
151
|
-
"""
|
|
152
|
-
header_value = get_client_id_from_headers()
|
|
153
|
-
if header_value:
|
|
154
|
-
return header_value
|
|
155
|
-
|
|
156
|
-
return resolve_cloud_client_id()
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
def resolve_client_secret() -> SecretString:
|
|
160
|
-
"""Resolve client secret from HTTP headers or environment variables.
|
|
161
|
-
|
|
162
|
-
Resolution order:
|
|
163
|
-
1. HTTP header X-Airbyte-Cloud-Client-Secret
|
|
164
|
-
2. Environment variable AIRBYTE_CLOUD_CLIENT_SECRET (via PyAirbyte)
|
|
165
|
-
|
|
166
|
-
Returns:
|
|
167
|
-
The resolved client secret as a SecretString.
|
|
168
|
-
|
|
169
|
-
Raises:
|
|
170
|
-
PyAirbyteSecretNotFoundError: If no client secret can be resolved.
|
|
171
|
-
"""
|
|
172
|
-
header_value = get_client_secret_from_headers()
|
|
173
|
-
if header_value:
|
|
174
|
-
return header_value
|
|
175
|
-
|
|
176
|
-
return resolve_cloud_client_secret()
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
def resolve_workspace_id(workspace_id: str | None = None) -> str:
|
|
180
|
-
"""Resolve workspace ID from multiple sources.
|
|
181
|
-
|
|
182
|
-
Resolution order:
|
|
183
|
-
1. Explicit workspace_id parameter (if provided)
|
|
184
|
-
2. HTTP header X-Airbyte-Cloud-Workspace-Id
|
|
185
|
-
3. Environment variable AIRBYTE_CLOUD_WORKSPACE_ID (via PyAirbyte)
|
|
186
|
-
|
|
187
|
-
Args:
|
|
188
|
-
workspace_id: Optional explicit workspace ID.
|
|
189
|
-
|
|
190
|
-
Returns:
|
|
191
|
-
The resolved workspace ID.
|
|
192
|
-
|
|
193
|
-
Raises:
|
|
194
|
-
PyAirbyteSecretNotFoundError: If no workspace ID can be resolved.
|
|
195
|
-
"""
|
|
196
|
-
if workspace_id is not None:
|
|
197
|
-
return workspace_id
|
|
198
|
-
|
|
199
|
-
header_value = get_workspace_id_from_headers()
|
|
200
|
-
if header_value:
|
|
201
|
-
return header_value
|
|
202
|
-
|
|
203
|
-
return resolve_cloud_workspace_id()
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
def resolve_api_url(api_url: str | None = None) -> str:
|
|
207
|
-
"""Resolve API URL from multiple sources.
|
|
208
|
-
|
|
209
|
-
Resolution order:
|
|
210
|
-
1. Explicit api_url parameter (if provided)
|
|
211
|
-
2. HTTP header X-Airbyte-Cloud-Api-Url
|
|
212
|
-
3. Environment variable / default (via PyAirbyte)
|
|
213
|
-
|
|
214
|
-
Args:
|
|
215
|
-
api_url: Optional explicit API URL.
|
|
216
|
-
|
|
217
|
-
Returns:
|
|
218
|
-
The resolved API URL.
|
|
219
|
-
"""
|
|
220
|
-
if api_url is not None:
|
|
221
|
-
return api_url
|
|
222
|
-
|
|
223
|
-
header_value = get_api_url_from_headers()
|
|
224
|
-
if header_value:
|
|
225
|
-
return header_value
|
|
226
|
-
|
|
227
|
-
return resolve_cloud_api_url()
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
def resolve_bearer_token() -> SecretString | None:
|
|
231
|
-
"""Resolve bearer token from HTTP headers or environment variables.
|
|
232
|
-
|
|
233
|
-
Resolution order:
|
|
234
|
-
1. HTTP Authorization header (Bearer <token>)
|
|
235
|
-
2. Environment variable AIRBYTE_CLOUD_BEARER_TOKEN
|
|
236
|
-
|
|
237
|
-
Returns:
|
|
238
|
-
The resolved bearer token as a SecretString, or None if not found.
|
|
239
|
-
|
|
240
|
-
Note:
|
|
241
|
-
Unlike resolve_client_id/resolve_client_secret, this function returns
|
|
242
|
-
None instead of raising an exception if no bearer token is found,
|
|
243
|
-
since bearer token auth is optional (can fall back to client credentials).
|
|
244
|
-
"""
|
|
245
|
-
header_value = get_bearer_token_from_headers()
|
|
246
|
-
if header_value:
|
|
247
|
-
return header_value
|
|
248
|
-
|
|
249
|
-
# Try env var directly
|
|
250
|
-
env_value = os.environ.get("AIRBYTE_CLOUD_BEARER_TOKEN")
|
|
251
|
-
if env_value:
|
|
252
|
-
return SecretString(env_value)
|
|
253
|
-
|
|
254
|
-
return None
|
|
@@ -1,398 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
-
"""Deferred MCP capability registration for tools, prompts, and resources.
|
|
3
|
-
|
|
4
|
-
This module provides a decorator to tag tool functions with MCP annotations
|
|
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.
|
|
7
|
-
"""
|
|
8
|
-
|
|
9
|
-
from __future__ import annotations
|
|
10
|
-
|
|
11
|
-
import inspect
|
|
12
|
-
from collections.abc import Callable
|
|
13
|
-
from dataclasses import dataclass
|
|
14
|
-
from enum import Enum
|
|
15
|
-
from pathlib import Path
|
|
16
|
-
from typing import Any, TypeVar
|
|
17
|
-
|
|
18
|
-
from fastmcp import FastMCP
|
|
19
|
-
|
|
20
|
-
from airbyte_ops_mcp._annotations import (
|
|
21
|
-
DESTRUCTIVE_HINT,
|
|
22
|
-
IDEMPOTENT_HINT,
|
|
23
|
-
OPEN_WORLD_HINT,
|
|
24
|
-
READ_ONLY_HINT,
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
F = TypeVar("F", bound=Callable[..., Any])
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
@dataclass
|
|
31
|
-
class PromptDef:
|
|
32
|
-
"""Definition of a deferred MCP prompt."""
|
|
33
|
-
|
|
34
|
-
name: str
|
|
35
|
-
description: str
|
|
36
|
-
func: Callable[..., list[dict[str, str]]]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@dataclass
|
|
40
|
-
class ResourceDef:
|
|
41
|
-
"""Definition of a deferred MCP resource."""
|
|
42
|
-
|
|
43
|
-
uri: str
|
|
44
|
-
description: str
|
|
45
|
-
mime_type: str
|
|
46
|
-
func: Callable[..., Any]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
class ToolDomain(str, Enum):
|
|
50
|
-
"""Tool domain categories for the Airbyte Admin MCP server.
|
|
51
|
-
|
|
52
|
-
These domains correspond to the main functional areas of the server.
|
|
53
|
-
"""
|
|
54
|
-
|
|
55
|
-
REGISTRY = "registry"
|
|
56
|
-
"""Registry tools for connector registry operations"""
|
|
57
|
-
|
|
58
|
-
METADATA = "metadata"
|
|
59
|
-
"""Metadata tools for connector metadata operations"""
|
|
60
|
-
|
|
61
|
-
QA = "qa"
|
|
62
|
-
"""QA tools for connector quality assurance"""
|
|
63
|
-
|
|
64
|
-
INSIGHTS = "insights"
|
|
65
|
-
"""Insights tools for connector analysis and insights"""
|
|
66
|
-
|
|
67
|
-
REPO = "repo"
|
|
68
|
-
"""Repository tools for GitHub repository operations"""
|
|
69
|
-
|
|
70
|
-
CLOUD_ADMIN = "cloud_admin"
|
|
71
|
-
"""Cloud admin tools for Airbyte Cloud operations"""
|
|
72
|
-
|
|
73
|
-
SERVER_INFO = "server_info"
|
|
74
|
-
"""Server information and version resources"""
|
|
75
|
-
|
|
76
|
-
PROMPTS = "prompts"
|
|
77
|
-
"""Prompt templates for common workflows"""
|
|
78
|
-
|
|
79
|
-
REGRESSION_TESTS = "regression_tests"
|
|
80
|
-
"""Regression tests for connector validation and comparison testing"""
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
_REGISTERED_TOOLS: list[tuple[Callable[..., Any], dict[str, Any]]] = []
|
|
84
|
-
_REGISTERED_RESOURCES: list[tuple[Callable[..., Any], dict[str, Any]]] = []
|
|
85
|
-
_REGISTERED_PROMPTS: list[tuple[Callable[..., Any], dict[str, Any]]] = []
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
def should_register_tool(annotations: dict[str, Any]) -> bool:
|
|
89
|
-
"""Check if a tool should be registered.
|
|
90
|
-
|
|
91
|
-
Args:
|
|
92
|
-
annotations: Tool annotations dict
|
|
93
|
-
|
|
94
|
-
Returns:
|
|
95
|
-
Always returns True (no filtering applied)
|
|
96
|
-
"""
|
|
97
|
-
return True
|
|
98
|
-
|
|
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
|
-
|
|
131
|
-
def mcp_tool(
|
|
132
|
-
domain: ToolDomain | str | None = None,
|
|
133
|
-
*,
|
|
134
|
-
read_only: bool = False,
|
|
135
|
-
destructive: bool = False,
|
|
136
|
-
idempotent: bool = False,
|
|
137
|
-
open_world: bool = False,
|
|
138
|
-
extra_help_text: str | None = None,
|
|
139
|
-
) -> Callable[[F], F]:
|
|
140
|
-
"""Decorator to tag an MCP tool function with annotations for deferred registration.
|
|
141
|
-
|
|
142
|
-
This decorator stores the annotations on the function for later use during
|
|
143
|
-
deferred registration. It does not register the tool immediately.
|
|
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
|
-
|
|
148
|
-
Args:
|
|
149
|
-
domain: Optional explicit domain override. If not provided, the domain
|
|
150
|
-
is automatically derived from the caller's file stem.
|
|
151
|
-
read_only: If True, tool only reads without making changes (default: False)
|
|
152
|
-
destructive: If True, tool modifies/deletes existing data (default: False)
|
|
153
|
-
idempotent: If True, repeated calls have same effect (default: False)
|
|
154
|
-
open_world: If True, tool interacts with external systems (default: False)
|
|
155
|
-
extra_help_text: Optional text to append to the function's docstring
|
|
156
|
-
with a newline delimiter
|
|
157
|
-
|
|
158
|
-
Returns:
|
|
159
|
-
Decorator function that tags the tool with annotations
|
|
160
|
-
|
|
161
|
-
Example:
|
|
162
|
-
@mcp_tool(read_only=True, idempotent=True)
|
|
163
|
-
def list_connectors_in_repo():
|
|
164
|
-
...
|
|
165
|
-
"""
|
|
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
|
-
|
|
174
|
-
annotations: dict[str, Any] = {
|
|
175
|
-
"domain": domain_str,
|
|
176
|
-
READ_ONLY_HINT: read_only,
|
|
177
|
-
DESTRUCTIVE_HINT: destructive,
|
|
178
|
-
IDEMPOTENT_HINT: idempotent,
|
|
179
|
-
OPEN_WORLD_HINT: open_world,
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
def decorator(func: F) -> F:
|
|
183
|
-
if extra_help_text:
|
|
184
|
-
func.__doc__ = (
|
|
185
|
-
(func.__doc__ or "") + "\n\n" + (extra_help_text or "")
|
|
186
|
-
).rstrip()
|
|
187
|
-
|
|
188
|
-
_REGISTERED_TOOLS.append((func, annotations))
|
|
189
|
-
return func
|
|
190
|
-
|
|
191
|
-
return decorator
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
def mcp_prompt(
|
|
195
|
-
name: str,
|
|
196
|
-
description: str,
|
|
197
|
-
domain: ToolDomain | str | None = None,
|
|
198
|
-
):
|
|
199
|
-
"""Decorator for deferred MCP prompt registration.
|
|
200
|
-
|
|
201
|
-
Args:
|
|
202
|
-
name: Unique name for the prompt
|
|
203
|
-
description: Human-readable description of the prompt
|
|
204
|
-
domain: Optional domain for filtering. If not provided, automatically
|
|
205
|
-
derived from the caller's file stem.
|
|
206
|
-
|
|
207
|
-
Returns:
|
|
208
|
-
Decorator function that registers the prompt
|
|
209
|
-
|
|
210
|
-
Raises:
|
|
211
|
-
ValueError: If a prompt with the same name is already registered
|
|
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
|
|
220
|
-
|
|
221
|
-
def decorator(func: Callable[..., list[dict[str, str]]]):
|
|
222
|
-
annotations = {
|
|
223
|
-
"name": name,
|
|
224
|
-
"description": description,
|
|
225
|
-
"domain": domain_str,
|
|
226
|
-
}
|
|
227
|
-
_REGISTERED_PROMPTS.append((func, annotations))
|
|
228
|
-
return func
|
|
229
|
-
|
|
230
|
-
return decorator
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def mcp_resource(
|
|
234
|
-
uri: str,
|
|
235
|
-
description: str,
|
|
236
|
-
mime_type: str,
|
|
237
|
-
domain: ToolDomain | str | None = None,
|
|
238
|
-
):
|
|
239
|
-
"""Decorator for deferred MCP resource registration.
|
|
240
|
-
|
|
241
|
-
Args:
|
|
242
|
-
uri: Unique URI for the resource
|
|
243
|
-
description: Human-readable description of the resource
|
|
244
|
-
mime_type: MIME type of the resource content
|
|
245
|
-
domain: Optional domain for filtering. If not provided, automatically
|
|
246
|
-
derived from the caller's file stem.
|
|
247
|
-
|
|
248
|
-
Returns:
|
|
249
|
-
Decorator function that registers the resource
|
|
250
|
-
|
|
251
|
-
Raises:
|
|
252
|
-
ValueError: If a resource with the same URI is already registered
|
|
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
|
|
261
|
-
|
|
262
|
-
def decorator(func: Callable[..., Any]):
|
|
263
|
-
annotations = {
|
|
264
|
-
"uri": uri,
|
|
265
|
-
"description": description,
|
|
266
|
-
"mime_type": mime_type,
|
|
267
|
-
"domain": domain_str,
|
|
268
|
-
}
|
|
269
|
-
_REGISTERED_RESOURCES.append((func, annotations))
|
|
270
|
-
return func
|
|
271
|
-
|
|
272
|
-
return decorator
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
def _register_mcp_callables(
|
|
276
|
-
*,
|
|
277
|
-
app: FastMCP,
|
|
278
|
-
domain: ToolDomain | str,
|
|
279
|
-
resource_list: list[tuple[Callable, dict]],
|
|
280
|
-
register_fn: Callable,
|
|
281
|
-
) -> None:
|
|
282
|
-
"""Register resources and tools with the FastMCP app, filtered by domain.
|
|
283
|
-
|
|
284
|
-
Args:
|
|
285
|
-
app: The FastMCP app instance
|
|
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__).
|
|
288
|
-
resource_list: List of (callable, annotations) tuples to register
|
|
289
|
-
register_fn: Function to call for each registration
|
|
290
|
-
"""
|
|
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)
|
|
294
|
-
|
|
295
|
-
filtered_callables = [
|
|
296
|
-
(func, ann) for func, ann in resource_list if ann.get("domain") == domain_str
|
|
297
|
-
]
|
|
298
|
-
|
|
299
|
-
for callable_fn, callable_annotations in filtered_callables:
|
|
300
|
-
register_fn(app, callable_fn, callable_annotations)
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
def register_mcp_tools(
|
|
304
|
-
app: FastMCP,
|
|
305
|
-
domain: ToolDomain | str | None = None,
|
|
306
|
-
) -> None:
|
|
307
|
-
"""Register tools with the FastMCP app, filtered by domain.
|
|
308
|
-
|
|
309
|
-
Args:
|
|
310
|
-
app: The FastMCP app instance
|
|
311
|
-
domain: The domain to register for. If not provided, automatically
|
|
312
|
-
derived from the caller's file stem.
|
|
313
|
-
"""
|
|
314
|
-
if domain is None:
|
|
315
|
-
domain = _get_caller_file_stem()
|
|
316
|
-
|
|
317
|
-
def _register_fn(
|
|
318
|
-
app: FastMCP,
|
|
319
|
-
callable_fn: Callable,
|
|
320
|
-
annotations: dict[str, Any],
|
|
321
|
-
):
|
|
322
|
-
app.tool(
|
|
323
|
-
callable_fn,
|
|
324
|
-
annotations=annotations,
|
|
325
|
-
)
|
|
326
|
-
|
|
327
|
-
_register_mcp_callables(
|
|
328
|
-
app=app,
|
|
329
|
-
domain=domain,
|
|
330
|
-
resource_list=_REGISTERED_TOOLS,
|
|
331
|
-
register_fn=_register_fn,
|
|
332
|
-
)
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
def register_mcp_prompts(
|
|
336
|
-
app: FastMCP,
|
|
337
|
-
domain: ToolDomain | str | None = None,
|
|
338
|
-
) -> None:
|
|
339
|
-
"""Register prompt callables with the FastMCP app, filtered by domain.
|
|
340
|
-
|
|
341
|
-
Args:
|
|
342
|
-
app: The FastMCP app instance
|
|
343
|
-
domain: The domain to register for. If not provided, automatically
|
|
344
|
-
derived from the caller's file stem.
|
|
345
|
-
"""
|
|
346
|
-
if domain is None:
|
|
347
|
-
domain = _get_caller_file_stem()
|
|
348
|
-
|
|
349
|
-
def _register_fn(
|
|
350
|
-
app: FastMCP,
|
|
351
|
-
callable_fn: Callable,
|
|
352
|
-
annotations: dict[str, Any],
|
|
353
|
-
):
|
|
354
|
-
app.prompt(
|
|
355
|
-
name=annotations["name"],
|
|
356
|
-
description=annotations["description"],
|
|
357
|
-
)(callable_fn)
|
|
358
|
-
|
|
359
|
-
_register_mcp_callables(
|
|
360
|
-
app=app,
|
|
361
|
-
domain=domain,
|
|
362
|
-
resource_list=_REGISTERED_PROMPTS,
|
|
363
|
-
register_fn=_register_fn,
|
|
364
|
-
)
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
def register_mcp_resources(
|
|
368
|
-
app: FastMCP,
|
|
369
|
-
domain: ToolDomain | str | None = None,
|
|
370
|
-
) -> None:
|
|
371
|
-
"""Register resource callables with the FastMCP app, filtered by domain.
|
|
372
|
-
|
|
373
|
-
Args:
|
|
374
|
-
app: The FastMCP app instance
|
|
375
|
-
domain: The domain to register for. If not provided, automatically
|
|
376
|
-
derived from the caller's file stem.
|
|
377
|
-
"""
|
|
378
|
-
if domain is None:
|
|
379
|
-
domain = _get_caller_file_stem()
|
|
380
|
-
|
|
381
|
-
def _register_fn(
|
|
382
|
-
app: FastMCP,
|
|
383
|
-
callable_fn: Callable,
|
|
384
|
-
annotations: dict[str, Any],
|
|
385
|
-
):
|
|
386
|
-
_ = annotations
|
|
387
|
-
app.resource(
|
|
388
|
-
annotations["uri"],
|
|
389
|
-
description=annotations["description"],
|
|
390
|
-
mime_type=annotations["mime_type"],
|
|
391
|
-
)(callable_fn)
|
|
392
|
-
|
|
393
|
-
_register_mcp_callables(
|
|
394
|
-
app=app,
|
|
395
|
-
domain=domain,
|
|
396
|
-
resource_list=_REGISTERED_RESOURCES,
|
|
397
|
-
register_fn=_register_fn,
|
|
398
|
-
)
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# Copyright (c) 2025 Airbyte, Inc., all rights reserved.
|
|
2
|
-
"""MCP resources for the Airbyte Admin MCP server.
|
|
3
|
-
|
|
4
|
-
This module provides read-only resources that can be accessed by MCP clients.
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import importlib.metadata as md
|
|
8
|
-
import subprocess
|
|
9
|
-
from functools import lru_cache
|
|
10
|
-
|
|
11
|
-
from fastmcp import FastMCP
|
|
12
|
-
|
|
13
|
-
from airbyte_ops_mcp.constants import MCP_SERVER_NAME
|
|
14
|
-
from airbyte_ops_mcp.mcp._mcp_utils import (
|
|
15
|
-
mcp_resource,
|
|
16
|
-
register_mcp_resources,
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
@lru_cache(maxsize=1)
|
|
21
|
-
def _get_version_info() -> dict[str, str | list[str] | None]:
|
|
22
|
-
"""Get version information for the MCP server.
|
|
23
|
-
|
|
24
|
-
Returns:
|
|
25
|
-
Dictionary with version information including package version,
|
|
26
|
-
git SHA, and FastMCP version
|
|
27
|
-
"""
|
|
28
|
-
package_name = "airbyte-internal-ops"
|
|
29
|
-
|
|
30
|
-
try:
|
|
31
|
-
version = md.version(package_name)
|
|
32
|
-
except md.PackageNotFoundError:
|
|
33
|
-
version = "0.1.0+dev"
|
|
34
|
-
|
|
35
|
-
try:
|
|
36
|
-
fastmcp_version = md.version("fastmcp")
|
|
37
|
-
except md.PackageNotFoundError:
|
|
38
|
-
fastmcp_version = None
|
|
39
|
-
|
|
40
|
-
try:
|
|
41
|
-
git_sha = subprocess.run(
|
|
42
|
-
["git", "rev-parse", "--short", "HEAD"],
|
|
43
|
-
capture_output=True,
|
|
44
|
-
text=True,
|
|
45
|
-
check=True,
|
|
46
|
-
timeout=5,
|
|
47
|
-
).stdout.strip()
|
|
48
|
-
except Exception:
|
|
49
|
-
git_sha = None
|
|
50
|
-
|
|
51
|
-
return {
|
|
52
|
-
"name": package_name,
|
|
53
|
-
"docs_url": "https://github.com/airbytehq/airbyte-ops-mcp",
|
|
54
|
-
"release_history_url": "https://github.com/airbytehq/airbyte-ops-mcp/releases",
|
|
55
|
-
"version": version,
|
|
56
|
-
"git_sha": git_sha,
|
|
57
|
-
"fastmcp_version": fastmcp_version,
|
|
58
|
-
"domains": ["registry", "metadata", "qa", "insights", "repo"],
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
@mcp_resource(
|
|
63
|
-
uri=f"{MCP_SERVER_NAME}://server/info",
|
|
64
|
-
description="Server information for the Airbyte Admin MCP server",
|
|
65
|
-
mime_type="application/json",
|
|
66
|
-
)
|
|
67
|
-
def mcp_server_info() -> dict[str, str | list[str] | None]:
|
|
68
|
-
"""Resource that returns information for the MCP server.
|
|
69
|
-
|
|
70
|
-
This includes package version, release history, help URLs, as well as other information.
|
|
71
|
-
|
|
72
|
-
Returns:
|
|
73
|
-
Dictionary with version information
|
|
74
|
-
"""
|
|
75
|
-
return _get_version_info()
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def register_server_info_resources(app: FastMCP) -> None:
|
|
79
|
-
"""Register server info resources with the FastMCP app.
|
|
80
|
-
|
|
81
|
-
Args:
|
|
82
|
-
app: FastMCP application instance
|
|
83
|
-
"""
|
|
84
|
-
register_mcp_resources(app, domain=__name__)
|
|
File without changes
|
{airbyte_internal_ops-0.5.2.dist-info → airbyte_internal_ops-0.6.0.dist-info}/entry_points.txt
RENAMED
|
File without changes
|