Connections: 관리형 자격 증명 및 호출자별 식별자를 위한 Managed Deep Agents
요약
Managed Deep Agents의 Connections 기능은 API 키를 코드나 환경 변수에 하드코딩할 필요 없이 LangSmith 작업 공간에 관리합니다. 이를 통해 에이전트가 어떤 사용자를 대신하여 행동하든, 해당 사용자 고유의 식별자로 추적 가능하며 OAuth 구현 복잡성을 크게 줄여줍니다.
핵심 포인트
- Connections는 API 키를 코드나 .env 파일에서 분리하여 LangSmith 작업 공간에 관리합니다.
- 사용자 소유 Connection은 요청한 사용자의 고유 ID로 에이전트 활동을 정확히 추적합니다.
- OAuth 구현 시 콜백 경로, 토큰 저장소 등 복잡한 로직을 직접 처리할 필요가 없습니다.
주요 요점
자격 증명을 프로젝트에 포함하지 마세요. Connection은 .env 파일이나 빌드에 있는 것이 아니라, LangSmith 작업 공간에 존재합니다. 코드를 건드리거나 재배포할 필요 없이 이를 순환시키거나 취소할 수 있습니다.
각 호출자에게 고유한 식별자를 부여하세요. 사용자 소유의 connection은 요청하는 사람으로 해결되므로, 에이전트가 생성하는 티켓에는 봇의 핸들 대신 해당 사용자의 핸들이 기록됩니다.
OAuth 구현 과정을 생략하세요. Managed Deep Agents가 권한 부여 왕복(authorization round-trip)을 처리합니다. 프로젝트에 콜백 경로, 토큰 저장소, 새로고침 로직, 동의 화면이 필요 없습니다.
Connections는 Managed Deep Agents v0.7.0 이상에서 사용 가능합니다.
모든 에이전트는 결국 누군가를 대신하여 행동해야 합니다. 웹 검색을 하거나, 티켓을 생성하거나, 풀 리퀘스트를 여는 등의 작업입니다. 오늘날 이는 보통 모든 배포에 하드코딩된 하나의 API 키를 의미하며, 모든 활동은 서비스 계정 명의로 표시됩니다. .env 파일의 키는 에이전트가 무엇을 할 수 있는지 알려줄 뿐, 누가 요청했는지 알 방법이 없습니다.

Connections가 바로 이 문제를 해결합니다. Connection은 LangSmith 작업 공간에 있는 이름 지정된 자격 증명으로, 도구들이 실행 시간에 슬러그(slug)를 통해 한 번의 호출로 읽어갑니다.
두 축
Connection은 소유자(owner)와 자격 증명 유형(credential type)을 가지며, 이 둘은 독립적입니다.
소유자는 에이전트일 수도 있고 호출자일 수도 있습니다. 에이전트 소유의 자격 증명은 배포에 속하며 모든 호출자가 공유합니다. 사용자 소유의 자격 증명은 실행 시간에 개인별로 해결됩니다.
자격 증명은 정적 비밀(static secret)이거나 OAuth 승인(OAuth grant)입니다. 에이전트는 OAuth 승인을 보유할 수 있고, 사용자는 비밀을 보유할 수 있습니다.
소유권은 mda connections create 및 connections.get()을 사용하여 Connection을 생성할 때 고정되며, connections.get()은 이미 존재하는 자격 증명 중에서 선택만 합니다.
에이전트 소유의 비밀

에이전트 소유의 비밀(agent-owned secret)은 모든 호출자가 공유해야 하는 하나의 자격 증명이 필요한 상황에서 사용됩니다. 이는 개인별로 차이가 없는 기능, 예를 들어 웹 검색, 지오코더, 가격 책정 피드와 같은 경우에 적합한 접근 방식입니다.
이 예시에서는 Tavily에 연결을 구성하여 에이전트에 일반 웹 검색 도구를 추가해 보겠습니다:
uv run mda connections create tavily-agent --secret-from-env TAVILY_API_KEY
여기서 tavily-agent는 슬러그(slug)입니다. 이는 연결에 대한 이름이자 코드가 사용하는 이름이며, 어떤 목록과도 비교되지 않습니다. 값은 TAVILY_API_KEY에서 나와 LangSmith 작업 공간으로 들어갑니다. 이는 빌드에 포함되는 부분이 아니며, mda deploy는 .env 파일을 배포 비밀(deployment secrets)로 가져오는 방식처럼 이를 처리하지 않습니다.
이 값을 읽는 도구는 connections.get을 활용하는 일반적인 LangChain 도구입니다:
# tools/search_web.py
import httpx
from langchain.tools import tool
from managed_deepagents import connections
@tool(parse_docstring=True)
async def search_web(query: str) -> str:
"""
웹을 검색합니다.
인수:
query: 검색어.
"""
api_key = await connections.get("tavily-agent", {"type": "agent"})
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.post(
"https://api.tavily.com/search",
json={"api_key": api_key, "query": query, "max_results": 5},
)
response.raise_for_status()
return response.text
만약 키를 순환(rotate)해야 한다면, tavily-agent에 저장된 비밀을 업데이트할 수 있으며, 향후 모든 에이전트 요청은 자동으로 새 키를 사용하게 됩니다.
사용자 소유 OAuth (User-owned OAuth)와 자체 앱
공유 토큰도 유용하지만, 에이전트가 사용자를 대신하여 행동하도록 허용하면 에이전트에 더 많은 기능을 안전하게 제공할 수 있습니다. GitHub는 22개 이상의 다른 서비스와 함께 connections 카탈로그에 포함되어 있어, 인증 URL이나 토큰 URL, 조회해야 할 인증 방식 없이 클라이언트 ID와 비밀만 가져오면 됩니다.
mda connections catalog로 카탈로그 연결을 빠르게 참조할 수 있지만, 자체 메타데이터를 가져온다면 OAuth를 제공하는 모든 공급자(provider)에 연결할 수 있습니다.
예를 들어, 사용자 정의 GitHub OAuth 앱에 대한 연결을 구성하려면 다음과 같습니다:
`uv run mda connections create github-issues \
이 예시에서 github-issues는 슬러그(slug)이며, 이는 사용자에게 할당되고 코드가 사용하는 이름입니다. github는 카탈로그 서비스로, 어떤 엔드포인트가 채워질지 결정하는 역할만 합니다.
-scope repo는 기존의 카탈로그 기본값에 추가되는 것이 아니라 이를 대체합니다. GitHub의 기본값은 read:user인데, 이는 이슈를 열 수 없으므로, 전달하는 모든 값이 전체 목록이 됩니다.
도구들은 헬퍼(helper)를 통해 토큰을 읽어옵니다. 이 예시에서 핵심 코드는 다음과 같습니다:
access_token = await connections.get("github-issues", {"type": "user"})
connections.get 호출 한 번만으로, 배포된 에이전트는 새로운 사용자에게는 OAuth 플로우를 자동으로 실행하거나, 이전에 해당 OAuth 제공업체에 대해 인증한 사용자의 캐시된 OAuth 토큰을 가져올 수 있습니다.
이 액세스 토큰을 활용하여 Github에 임의의 API 호출을 할 수 있습니다:
# tools/github.py
async def _github(method: str, path: str, **kwargs) -> dict:
access_token = await connections.get("github-issues", {"type": "user"})
async with httpx.AsyncClient(timeout=30.0) as client:
response = await client.request(
method,
f"{GITHUB_API}{path}",
headers={
"Authorization": f"Bearer {access_token}",
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": GITHUB_VERSION,
},
**kwargs,
)
response.raise_for_status()
return response.json()
{"type": "user"}를 설정한 것에 주목하십시오. 에이전트가 소유한 연결(connection)은 생성 시점에 값을 저장했습니다. 이 경우는 아무 값도 저장하지 않고 앱 등록 정보만 저장했습니다. 자격 증명(credential)은 호출자별로, 실행 시간에 도착하며 — 만약 호출자가 GitHub를 인증한 적이 없거나 토큰이 만료된 경우, connections.get()은 실패하는 대신 실행을 일시 중지하고 승인(grant)을 요청합니다.
해당 단어는 _github 내부에서 한 번 나타납니다. search_issues 도구와 create_issue 도구 모두 헬퍼로부터 호출자별 신원(identity)을 상속받으며, 세 번째 GitHub 도구는 인증 코드 비용이 전혀 들지 않을 것입니다.
이러한 이점은 두 곳에서 나타납니다. search_issues
아무것도 작성되기 전부터 이미 호출자별로 다릅니다. 왜냐하면 한 사람이 볼 수 있는 비공개 리포지토리와 다른 사람은 볼 수 없는 것이 있기 때문에, 쿼리가 같고 배포도 같더라도 답변은 달라집니다. 그리고 create_issue가
실행되면, 해당 이슈는 요청한 사람에게 의해 GitHub에 열립니다. 응답의 user.login은 봇이 아닌 그들의 핸들입니다.
앱 등록이 필요 없는 사용자 소유 OAuth
일부 MCP 서버는 자체적으로 OAuth 클라이언트를 등록합니다. 그렇게 할 경우, 전체 설정이 하나의 URL로 이루어집니다.
uv run mda connections create linear-mcp --mcp <https://mcp.linear.app/mcp>
# tools/mcp.py
from managed_deepagents import connections, define_mcp
mcp = define_mcp(
servers={
"linear": {
"transport": "http",
"url": "https://mcp.linear.app/mcp",
"connection": connections.get("linear-mcp", {"type": "user"}),
},
},
)
클라이언트 ID도, 클라이언트 시크릿도, 앱 등록도 필요 없습니다. 서버가 자체적으로 OAuth 메타데이터를 광고하고 클라이언트를 위해 자동으로 등록되기 때문에, 스코프(scope)도 필요하지 않으며, 연결은 서버 자체의 메타데이터로부터 read와 write로 협상되어 나옵니다.
이것을 GitHub 플로우와 비교해 봅시다. 하나는 자체 앱이 필요했고 다른 하나는 아무것도 필요 없었지만, 이 둘을 읽는 코드 라인은 동일합니다. 도구 코드가 여기서 사라지는 부분입니다. GitHub는 헬퍼(helper)와 두 개의 함수를 사용했지만, 이것은 서버 URL을 사용하며 도구들이 MCP 서버로부터 도착합니다.
한 번의 일시 중지, 누락된 권한마다
에이전트에게 양쪽 서비스를 아우르는 무언가를 요청하면, 첫 번째 모델 턴(model turn) 이전에 실행이 일시 중지되고, 호출자가 부여하지 않은 모든 연결 목록을 담은 단일 인터럽트가 발생합니다. 사용자가 권한을 승인하면 실행은 멈췄던 지점부터 재개됩니다.
프로젝트에는 콜백 경로도 없고, 토큰 저장소(token store)도 없으며, 새로 고침 로직(refresh logic)도 없고, 동의 화면(consent screen)도 없습니다. 호출자는 LangSmith를 열 필요가 전혀 없습니다.
두 번째 호출자에게도 같은 일을 하면, 동일한 에이전트, 동일한 슬러그, 그리고 동일한 워크스페이스 항목에서 다른 작성자를 가진 두 번째 문제가 발생합니다. 이는 모든 사용자에게 동일하게 설계된 Tavily 키와 대조적입니다. 사용자가 또는 다른 개발자가 LangSmith에 추가한 연결(connections)은 다음 명령어로 확인할 수 있습니다:
uv run mda connections list
시작하기
Connections는 Managed Deep Agents 프리릴리스에 포함되며, OAuth 카탈로그는 바이너리 내부에 포함되므로 사용 중인 버전에 따라 --oauth가 허용하는 것이 결정됩니다:
uv tool install managed-deepagents
uv run mda connections catalog
에이전트 소유 자격 증명(credential)은 배포(deployment)에 속하므로, 하나를 생성하기 전에 스캐폴드하고 한 번 배포해야 합니다. 그 후에는 각 연결이 세 단계를 거칩니다. 연결을 만들고(connections.get()로 읽어 들인 다음), 그것을 읽는 코드를 배포합니다.
로컬 개발도 같은 방식으로 작동합니다. 에이전트 소유 연결은 .env 파일에서 MDA_DEV_<SLUG> 형태로, 하이픈 대신 언더스코어로 대문자 처리되어 해결됩니다. 사용자 소유 연결은 로그인한 개발자를 mda dev 아래의 실제 주체(principal)에 해결하므로, 권한 부여 중단(authorization interrupt)이 로컬에서 발생하고 저장되는 승인(grant)도 실제 승인이 됩니다.
위의 세 가지 흐름 외에도, --authorize는 배포를 위한 하나의 OAuth 승인을 저장하므로 모든 호출자가 단일 공유 계정처럼 작동합니다. 이는 소유자-기반 자격 증명 모델의 네 번째 셀이며, 사용자별 신원(identity) 대신 전용 팀 계정을 원할 때 올바른 답변입니다. --allowed-scope는 나중에 승인할 수 있는 범위를 제한하며, --authorize-url과 --token-url은 카탈로그 외부에 있는 모든 제공업체를 포괄합니다.
더 자세한 내용과 예시는 Connections 문서를 https://docs.langchain.com/langsmith/python/managed-deep-agents-connections에서 확인할 수 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 LangChain Blog의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기