Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions airbyte/mcp/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@
from __future__ import annotations

import asyncio
import os
import sys

from fastmcp import FastMCP
from fastmcp_extensions import mcp_server

from airbyte._util.meta import set_mcp_mode
Expand Down Expand Up @@ -94,6 +96,47 @@
register_prompts(app)


def _mount_kapa_proxy(app: FastMCP) -> None:
"""Mount the Kapa knowledge MCP proxy if credentials are configured.

When KAPA_API_KEY is set, this creates a proxy to Kapa's hosted MCP server
and mounts it into the main server, giving agents inline access to Airbyte
documentation search alongside native PyAirbyte tools.

If KAPA_API_KEY is not set, this is a no-op.
"""
kapa_api_key = os.environ.get("KAPA_API_KEY")
if not kapa_api_key:
print(
"Kapa knowledge proxy not mounted (KAPA_API_KEY not set).",
file=sys.stderr,
)
return

kapa_url = os.environ.get("KAPA_MCP_SERVER_URL", "https://airbyte.mcp.kapa.ai")
kapa_proxy = FastMCP.as_proxy(
{
"mcpServers": {
"kapa": {
"url": kapa_url,
"transport": "http",
"headers": {
"Authorization": f"Bearer {kapa_api_key}",
},
}
}
}
)
app.mount(kapa_proxy, prefix="kapa")
Comment on lines +117 to +130
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat airbyte/mcp/server.py | head -150

Repository: airbytehq/PyAirbyte

Length of output: 5110


🏁 Script executed:

# Check for fastmcp dependency version
find . -name "*.txt" -o -name "*.toml" -o -name "*.cfg" -o -name "*.in" | xargs grep -l "fastmcp" 2>/dev/null | head -5

Repository: airbytehq/PyAirbyte

Length of output: 79


🏁 Script executed:

# Search for any existing error handling patterns in the file
rg "try:" airbyte/mcp/server.py -A 5 | head -50

Repository: airbytehq/PyAirbyte

Length of output: 320


🏁 Script executed:

grep -A 2 -B 2 "fastmcp" pyproject.toml | head -20

Repository: airbytehq/PyAirbyte

Length of output: 957


🏁 Script executed:

# Check if there are any other usages of FastMCP or as_proxy in the codebase
rg "FastMCP|as_proxy" --type py

Repository: airbytehq/PyAirbyte

Length of output: 1791


🏁 Script executed:

# Look for any test files related to mcp
find . -path "*/test*" -name "*mcp*" -type f | head -10

Repository: airbytehq/PyAirbyte

Length of output: 154


🌐 Web query:

FastMCP 2.11.3 as_proxy lazy initialization documentation

💡 Result:

FastMCP 2.11.3 (released 2025-08-11) is documented as having proxy support via FastMCP.as_proxy(), but the docs/API reference do not describe an explicit lazy= / “lazy initialization” option for as_proxy itself. [1][2]

What is documented (and effectively gives you “lazy” backend startup semantics) is that proxying is driven by a client factory / disconnected client, so the backend session/handshake happens when a request is made (e.g., tools/list, tools/call), not at proxy object construction:

  • Recommended pattern: pass a disconnected ProxyClient(...) into FastMCP.as_proxy(...) (fresh isolated backend session per request). [3]
  • For explicit control, build a proxy with a client_factory (fresh or shared client) via FastMCPProxy / create_proxy(...). [3][2]

If what you mean by “lazy initialization” is delaying the MCP initialize handshake of a Client, that is documented via auto_initialize=False (but that applies to using Client, not a special as_proxy flag). [4]

Key docs to read:

  • Proxy servers guide (covers FastMCP.as_proxy, ProxyClient, session behavior, and FastMCPProxy + client_factory). [3]
  • SDK reference for create_proxy(...) (recommended helper) and as_proxy(...) signature. [2][1]
  • Client “auto initialization” control (auto_initialize=False). [4]

Sources
[1] https://fastmcp.wiki/en/python-sdk/fastmcp-server-server
[2] https://fastmcp.mintlify.app/python-sdk/fastmcp-server-server
[3] https://fastmcp.wiki/en/servers/proxy
[4] https://fastmcp.mintlify.app/clients/client


🏁 Script executed:

# Check test files for any hints about error handling or proxy behavior
cat tests/unit_tests/test_mcp_connector_registry.py | head -50

Repository: airbytehq/PyAirbyte

Length of output: 1603


🏁 Script executed:

# Search for any environment setup or initialization patterns in tests
rg "try|except|error" tests/unit_tests/test_mcp*.py

Repository: airbytehq/PyAirbyte

Length of output: 1012


Add error handling around proxy creation and mounting to prevent optional dependency from breaking server startup.

Since the Kapa integration is optional and _mount_kapa_proxy() is invoked at module level, if FastMCP.as_proxy() or app.mount() throws (invalid config, network issues, etc.), the entire server fails to start. Worth wrapping in try/except to gracefully skip Kapa when unavailable, wdyt?

🛡️ Proposed defensive wrapper
+    try:
         kapa_proxy = FastMCP.as_proxy(
             {
                 "mcpServers": {
                     "kapa": {
                         "url": kapa_url,
                         "transport": "http",
                         "headers": {
                             "Authorization": f"Bearer {kapa_api_key}",
                         },
                     }
                 }
             }
         )
         app.mount(kapa_proxy, prefix="kapa")
         print(
             f"Kapa knowledge proxy mounted from {kapa_url}.",
             file=sys.stderr,
         )
+    except Exception as ex:
+        print(
+            f"Failed to mount Kapa knowledge proxy: {ex}",
+            file=sys.stderr,
+        )

Note: FastMCP.as_proxy() in v2.11.3 uses a lazy proxy pattern—the backend session/handshake happens when a request is made (not at construction)—so as_proxy() itself won't trigger eager connections. However, the app.mount() call should still be protected since it's at initialization time and failures there would crash startup.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@airbyte/mcp/server.py` around lines 117 - 130, Wrap the FastMCP.as_proxy(...)
creation and app.mount(kapa_proxy, prefix="kapa") call inside a try/except that
catches Exception (e.g., in _mount_kapa_proxy), so failures creating or mounting
the Kapa proxy do not crash server startup; on exception log a warning including
the exception details (use the module/server logger or logging.warning) and skip
mounting (return/continue) so the server starts without Kapa. Ensure you
reference FastMCP.as_proxy and app.mount in the handler so only the optional
integration is protected.

print(
f"Kapa knowledge proxy mounted from {kapa_url}.",
file=sys.stderr,
)


_mount_kapa_proxy(app)


def main() -> None:
"""@private Main entry point for the MCP server.

Expand Down