mirror of
https://github.com/Sea-Haven-Industries/open-swe.git
synced 2026-09-30 12:43:16 +00:00
* Add Corridor MCP analyzePlan integration * Update agent/integrations/corridor_mcp.py Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com> * Add Corridor analysis prompt * Only include Corridor prompt when tool loads --------- Co-authored-by: open-swe[bot] <215916821+open-swe[bot]@users.noreply.github.com>
126 lines
3.8 KiB
Python
126 lines
3.8 KiB
Python
"""Server-side Corridor tools backed by Corridor's hosted MCP server.
|
|
|
|
Credentials are read from environment variables and attached as an
|
|
``Authorization: Bearer ...`` header to the MCP connection, which runs in the
|
|
LangGraph server process. The sandbox never holds Corridor credentials.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
import os
|
|
from dataclasses import dataclass
|
|
from datetime import timedelta
|
|
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
|
|
|
|
from langchain_core.tools import BaseTool
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
DEFAULT_CORRIDOR_MCP_URL = "https://app.corridor.dev/api/mcp"
|
|
_CORRIDOR_HOST = "app.corridor.dev"
|
|
_CORRIDOR_PATH = "/api/mcp"
|
|
_MCP_TIMEOUT_SECONDS = 30.0
|
|
_TOKEN_ENV_NAMES = (
|
|
"CORRIDOR_API_TOKEN",
|
|
"CORRIDOR_MCP_TOKEN",
|
|
"CORRIDOR_TOKEN",
|
|
)
|
|
_TOKEN_QUERY_PARAMS = frozenset({"token", "api_key"})
|
|
_URL_ENV_NAMES = (
|
|
"CORRIDOR_MCP_URL",
|
|
"CORRIDOR_MCP_SERVER_URL",
|
|
)
|
|
_ALLOWED_TOOL_NAMES = frozenset({"analyzePlan"})
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class CorridorMCPConfig:
|
|
url: str
|
|
token: str
|
|
|
|
|
|
def _first_env_value(names: tuple[str, ...]) -> str:
|
|
for name in names:
|
|
value = os.environ.get(name, "").strip()
|
|
if value:
|
|
return value
|
|
return ""
|
|
|
|
|
|
def _extract_token_from_query(url: str) -> tuple[str, str]:
|
|
parsed = urlparse(url)
|
|
query = parse_qs(parsed.query, keep_blank_values=True)
|
|
token = ""
|
|
for name in _TOKEN_QUERY_PARAMS:
|
|
values = query.pop(name, [])
|
|
if not token:
|
|
token = next((value.strip() for value in values if value.strip()), "")
|
|
cleaned_query = urlencode(query, doseq=True)
|
|
cleaned_url = urlunparse(parsed._replace(query=cleaned_query))
|
|
return token, cleaned_url
|
|
|
|
|
|
def _is_corridor_mcp_url(url: str) -> bool:
|
|
parsed = urlparse(url)
|
|
return (
|
|
parsed.scheme == "https"
|
|
and parsed.hostname == _CORRIDOR_HOST
|
|
and parsed.path.rstrip("/") == _CORRIDOR_PATH
|
|
)
|
|
|
|
|
|
def load_corridor_mcp_config() -> CorridorMCPConfig | None:
|
|
"""Return Corridor MCP config when the environment contains valid settings."""
|
|
url = _first_env_value(_URL_ENV_NAMES) or DEFAULT_CORRIDOR_MCP_URL
|
|
token = _first_env_value(_TOKEN_ENV_NAMES)
|
|
query_token, url = _extract_token_from_query(url)
|
|
if not token:
|
|
token = query_token
|
|
if not token:
|
|
return None
|
|
if not _is_corridor_mcp_url(url):
|
|
logger.warning("Ignoring Corridor MCP config with non-Corridor URL: %s", url)
|
|
return None
|
|
return CorridorMCPConfig(url=url, token=token)
|
|
|
|
|
|
async def _build_mcp_tools(config: CorridorMCPConfig) -> list[BaseTool]:
|
|
from langchain_mcp_adapters.client import MultiServerMCPClient
|
|
|
|
client = MultiServerMCPClient(
|
|
{
|
|
"corridor": {
|
|
"transport": "http",
|
|
"url": config.url,
|
|
"headers": {
|
|
"Authorization": f"Bearer {config.token}",
|
|
},
|
|
"timeout": timedelta(seconds=_MCP_TIMEOUT_SECONDS),
|
|
}
|
|
}
|
|
)
|
|
return await client.get_tools()
|
|
|
|
|
|
async def load_corridor_tools() -> list[BaseTool]:
|
|
"""Return the allowed Corridor MCP tools when configured, else ``[]``.
|
|
|
|
Failures (missing config, unreachable MCP server) degrade to an empty list so
|
|
the agent still starts without Corridor tools.
|
|
"""
|
|
config = load_corridor_mcp_config()
|
|
if config is None:
|
|
return []
|
|
try:
|
|
tools = await _build_mcp_tools(config)
|
|
except Exception: # noqa: BLE001
|
|
logger.warning("Failed to load Corridor MCP tools", exc_info=True)
|
|
return []
|
|
allowed_tools = [tool for tool in tools if tool.name in _ALLOWED_TOOL_NAMES]
|
|
logger.info(
|
|
"Loaded %d Corridor MCP tool(s), exposing %d allowed tool(s)",
|
|
len(tools),
|
|
len(allowed_tools),
|
|
)
|
|
return allowed_tools
|